Merge "Measure baseline IMF latency (1/n)"

This commit is contained in:
Taran Singh
2020-11-12 18:59:22 +00:00
committed by Android (Google) Code Review
9 changed files with 913 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
// Copyright (C) 2020 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.
android_test {
name: "ImePerfTests",
srcs: ["src/**/*.java"],
static_libs: [
"androidx.test.rules",
"androidx.annotation_annotation",
"apct-perftests-utils",
"collector-device-lib",
"compatibility-device-util-axt",
"platform-test-annotations",
],
test_suites: ["device-tests"],
data: [":perfetto_artifacts"],
platform_apis: true,
certificate: "platform",
}

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.perftests.inputmethod">
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.perftests.utils.PerfTestActivity"
android:exported="true">
<intent-filter>
<action android:name="com.android.perftests.core.PERFTEST" />
</intent-filter>
</activity>
<service android:name="android.inputmethod.ImePerfTest$BaselineIme"
android:process=":BaselineIME"
android:label="Baseline IME"
android:permission="android.permission.BIND_INPUT_METHOD"
android:exported="true">
<intent-filter>
<action android:name="android.view.InputMethod"/>
</intent-filter>
<meta-data android:name="android.view.im"
android:resource="@xml/simple_method"/>
</service>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.perftests.inputmethod">
<meta-data android:name="listener" android:value="android.inputmethod.ImePerfRunPrecondition" />
</instrumentation>
</manifest>

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 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.
-->
<configuration description="Runs ImePerfTests metric instrumentation.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-metric-instrumentation" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="ImePerfTests.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<option name="force-skip-system-props" value="true" />
<option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
<option name="run-command" value="cmd window dismiss-keyguard" />
<option name="run-command" value="cmd package compile -m speed com.android.perftests.inputmethod" />
</target_preparer>
<!-- Needed for pushing the trace config file -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
<!--Install the content provider automatically when we push some file in sdcard folder.-->
<!--Needed to avoid the installation during the test suite.-->
<option name="push-file" key="trace_config_detailed.textproto" value="/sdcard/sample.textproto" />
</target_preparer>
<!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
<option name="isolated-storage" value="false" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.perftests.inputmethod" />
<option name="hidden-api-checks" value="false"/>
<!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
<option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
<!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
<option name="instrumentation-arg" key="newRunListenerMode" value="true" />
<!-- Kill background operations -->
<option name="instrumentation-arg" key="kill-bg" value="true" />
<!-- ProcLoadListener related arguments -->
<!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
<option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
<option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
<option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
<option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
<!-- PerfettoListener related arguments -->
<option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
<option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
</test>
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
<option name="directory-keys" value="/data/local/tmp/ImePerfTests" />
<!-- Needed for pulling the collected trace config on to the host -->
<option name="pull-pattern-keys" value="perfetto_file_path" />
</metrics_collector>
</configuration>

View File

@@ -0,0 +1,40 @@
## IMF performance tests
These tests are adaptation of Window Manager perf tests (apct-tests/perftests/windowmanager).
### Precondition
To reduce the variance of the test, if `perf-setup` (platform_testing/scripts/perf-setup)
is available, it is better to use the following instructions to lock CPU and GPU frequencies.
```
m perf-setup
PERF_SETUP_PATH=/data/local/tmp/perf-setup.sh
adb push $OUT/$PERF_SETUP_PATH $PERF_SETUP_PATH
adb shell chmod +x $PERF_SETUP_PATH
adb shell $PERF_SETUP_PATH
```
### Example to run
Use `atest`
```
atest ImePerfTests:ImePerfTest -- \
--module-arg ImePerfTests:instrumentation-arg:profiling-iterations:=20
```
Note: `instrumentation-arg:kill-bg:=true` is already defined in the AndroidText.xml
Use `am instrument`
```
adb shell am instrument -w -r -e class android.inputmethod.ImePerfTest \
-e listener android.inputmethod.ImePerfRunPrecondition \
-e kill-bg true \
com.android.perftests.inputmethod/androidx.test.runner.AndroidJUnitRunner
```
* `kill-bg` is optional.
Test arguments
- kill-bg
* boolean: Kill background process before running test.
- profiling-iterations
* int: Run the extra iterations with enabling method profiling.
- profiling-sampling
* int: The interval (0=trace each method, default is 10) of sample profiling in microseconds.

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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.
-->
<!-- Configuration info for an input method -->
<input-method xmlns:android="http://schemas.android.com/apk/res/android" />

View File

@@ -0,0 +1,149 @@
/*
* Copyright (C) 2020 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.inputmethod;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.inputmethod.ImePerfTestBase.executeShellCommand;
import static android.inputmethod.ImePerfTestBase.runWithShellPermissionIdentity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.inputmethod.ImePerfTestBase.SettingsSession;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import android.view.WindowManagerPolicyConstants;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.policy.PhoneWindow;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.notification.RunListener;
import java.util.List;
/** Prepare the preconditions before running performance test. */
public class ImePerfRunPrecondition extends RunListener {
private static final String TAG = ImePerfRunPrecondition.class.getSimpleName();
private static final String ARGUMENT_LOG_ONLY = "log";
private static final String ARGUMENT_KILL_BACKGROUND = "kill-bg";
private static final String ARGUMENT_PROFILING_ITERATIONS = "profiling-iterations";
private static final String ARGUMENT_PROFILING_SAMPLING = "profiling-sampling";
private static final String DEFAULT_PROFILING_ITERATIONS = "10";
private static final String DEFAULT_PROFILING_SAMPLING_US = "10";
private static final long KILL_BACKGROUND_WAIT_MS = 3000;
/** The requested iterations to run with method profiling. */
static int sProfilingIterations;
/** The interval of sample profiling in microseconds. */
static int sSamplingIntervalUs;
private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
private long mWaitPreconditionDoneMs = 500;
private final SettingsSession<Integer> mStayOnWhilePluggedInSetting = new SettingsSession<>(
Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0),
value -> executeShellCommand(String.format("settings put global %s %d",
Settings.Global.STAY_ON_WHILE_PLUGGED_IN, value)));
private final SettingsSession<Integer> mNavigationModeSetting = new SettingsSession<>(
mContext.getResources().getInteger(
com.android.internal.R.integer.config_navBarInteractionMode),
value -> {
final String navOverlay;
switch (value) {
case WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL:
default:
navOverlay = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
break;
}
executeShellCommand("cmd overlay enable-exclusive " + navOverlay);
});
/** It only executes once before all tests. */
@Override
public void testRunStarted(Description description) {
final Bundle arguments = InstrumentationRegistry.getArguments();
// If true, it only logs the method names without running.
final boolean skip = Boolean.parseBoolean(arguments.getString(ARGUMENT_LOG_ONLY, "false"));
Log.i(TAG, "arguments=" + arguments);
if (skip) {
return;
}
sProfilingIterations = Integer.parseInt(
arguments.getString(ARGUMENT_PROFILING_ITERATIONS, DEFAULT_PROFILING_ITERATIONS));
sSamplingIntervalUs = Integer.parseInt(
arguments.getString(ARGUMENT_PROFILING_SAMPLING, DEFAULT_PROFILING_SAMPLING_US));
// Use same navigation mode (gesture navigation) across all devices and tests
// for consistency.
mNavigationModeSetting.set(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL);
// Keep the device awake during testing.
mStayOnWhilePluggedInSetting.set(BatteryManager.BATTERY_PLUGGED_ANY);
runWithShellPermissionIdentity(() -> {
final ActivityTaskManager atm = mContext.getSystemService(ActivityTaskManager.class);
atm.removeAllVisibleRecentTasks();
atm.removeRootTasksWithActivityTypes(new int[] { ACTIVITY_TYPE_STANDARD,
ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS, ACTIVITY_TYPE_UNDEFINED });
});
PhoneWindow.sendCloseSystemWindows(mContext, "ImePerfTests");
if (Boolean.parseBoolean(arguments.getString(ARGUMENT_KILL_BACKGROUND))) {
runWithShellPermissionIdentity(this::killBackgroundProcesses);
mWaitPreconditionDoneMs = KILL_BACKGROUND_WAIT_MS;
}
// Wait a while for the precondition setup to complete.
SystemClock.sleep(mWaitPreconditionDoneMs);
}
private void killBackgroundProcesses() {
Log.i(TAG, "Killing background processes...");
final ActivityManager am = mContext.getSystemService(ActivityManager.class);
final List<RunningAppProcessInfo> processes = am.getRunningAppProcesses();
if (processes == null) {
return;
}
for (RunningAppProcessInfo processInfo : processes) {
if (processInfo.importanceReasonCode == RunningAppProcessInfo.REASON_UNKNOWN
&& processInfo.importance > RunningAppProcessInfo.IMPORTANCE_SERVICE) {
for (String pkg : processInfo.pkgList) {
am.forceStopPackage(pkg);
}
}
}
}
/** It only executes once after all tests. */
@Override
public void testRunFinished(Result result) {
mNavigationModeSetting.close();
mStayOnWhilePluggedInSetting.close();
}
}

View File

@@ -0,0 +1,380 @@
/*
* Copyright (C) 2020 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.inputmethod;
import static android.perftests.utils.ManualBenchmarkState.StatsReport;
import static android.perftests.utils.PerfTestActivity.ID_EDITOR;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.inputmethodservice.InputMethodService;
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.provider.Settings;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.WindowInsetsController;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.filters.LargeTest;
import com.android.compatibility.common.util.PollingCheck;
import junit.framework.Assert;
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.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
/** Measure the performance of internal methods in Input Method framework by trace tag. */
@LargeTest
public class ImePerfTest extends ImePerfTestBase
implements ManualBenchmarkState.CustomizedIterationListener {
private static final String TAG = ImePerfTest.class.getSimpleName();
@Rule
public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
@Rule
public final PerfTestActivityRule mActivityRule = new PerfTestActivityRule();
/**
* IMF common methods to log for show/hide in trace.
*/
private String[] mCommonMethods = {
"IC.pendingAnim",
"IMMS.applyImeVisibility",
"applyPostLayoutPolicy",
"applyWindowSurfaceChanges",
"ISC.onPostLayout"
};
/** IMF show methods to log in trace. */
private String[] mShowMethods = {
"IC.showRequestFromIme",
"IC.showRequestFromApi",
"IC.showRequestFromApiToImeReady",
"IC.pendingAnim",
"IMMS.applyImeVisibility",
"IMMS.showMySoftInput",
"IMMS.showSoftInput",
"IMS.showSoftInput",
"IMS.startInput",
"WMS.showImePostLayout"
};
/** IMF hide lifecycle methods to log in trace. */
private String[] mHideMethods = {
"IC.hideRequestFromIme",
"IC.hideRequestFromApi",
"IMMS.hideMySoftInput",
"IMMS.hideSoftInput",
"IMS.hideSoftInput",
"WMS.hideIme"
};
/**
* IMF methods to log in trace.
*/
private TraceMarkParser mTraceMethods;
private boolean mIsTraceStarted;
/**
* Ime Session for {@link BaselineIme}.
*/
private static class ImeSession implements AutoCloseable {
private static final long TIMEOUT = 2000;
private final ComponentName mImeName;
private Context mContext = getInstrumentation().getContext();
ImeSession(ComponentName ime) throws Exception {
mImeName = ime;
// using adb, enable and set Baseline IME.
executeShellCommand("ime reset");
executeShellCommand("ime enable " + ime.flattenToShortString());
executeShellCommand("ime set " + ime.flattenToShortString());
PollingCheck.check("Make sure that BaselineIme becomes available "
+ getCurrentInputMethodId(), TIMEOUT,
() -> ime.equals(getCurrentInputMethodId()));
}
@Override
public void close() throws Exception {
executeShellCommand("ime reset");
PollingCheck.check("Make sure that Baseline IME becomes unavailable", TIMEOUT, () ->
mContext.getSystemService(InputMethodManager.class)
.getEnabledInputMethodList()
.stream()
.noneMatch(info -> mImeName.equals(info.getComponent())));
}
@Nullable
private ComponentName getCurrentInputMethodId() {
return ComponentName.unflattenFromString(
Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.DEFAULT_INPUT_METHOD));
}
}
/**
* A minimal baseline IME (that has a single static view) used to measure IMF latency.
*/
public static class BaselineIme extends InputMethodService {
public static final int HEIGHT_DP = 100;
@Override
public View onCreateInputView() {
final ViewGroup view = new FrameLayout(this);
final View inner = new View(this);
final float density = getResources().getDisplayMetrics().density;
final int height = (int) (HEIGHT_DP * density);
view.setPadding(0, 0, 0, 0);
view.addView(inner, new FrameLayout.LayoutParams(MATCH_PARENT, height));
inner.setBackgroundColor(0xff01fe10); // green
return view;
}
static ComponentName getName(Context context) {
return new ComponentName(context, BaselineIme.class);
}
}
@Test
@ManualBenchmarkTest(
targetTestDurationNs = 10 * TIME_1_S_IN_NS,
statsReport = @StatsReport(
flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN
| StatsReport.FLAG_MIN | StatsReport.FLAG_MAX
| StatsReport.FLAG_COEFFICIENT_VAR))
public void testShowIme() throws Throwable {
testShowOrHideIme(true /* show */);
}
@Test
@ManualBenchmarkTest(
targetTestDurationNs = 10 * TIME_1_S_IN_NS,
statsReport = @StatsReport(
flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN
| StatsReport.FLAG_MIN | StatsReport.FLAG_MAX
| StatsReport.FLAG_COEFFICIENT_VAR))
public void testHideIme() throws Throwable {
testShowOrHideIme(false /* show */);
}
private void testShowOrHideIme(final boolean show) throws Throwable {
mTraceMethods = new TraceMarkParser(buildArray(
mCommonMethods, show ? mShowMethods : mHideMethods));
final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
state.setCustomizedIterations(getProfilingIterations(), this);
long measuredTimeNs = 0;
try (ImeSession imeSession = new ImeSession(BaselineIme.getName(
getInstrumentation().getContext()))) {
final AtomicReference<CountDownLatch> latchStart = new AtomicReference<>();
final AtomicReference<CountDownLatch> latchEnd = new AtomicReference<>();
final Activity activity = getActivityWithFocus();
// call IME show/hide
final WindowInsetsController controller =
activity.getWindow().getDecorView().getWindowInsetsController();
while (state.keepRunning(measuredTimeNs)) {
setImeListener(activity, latchStart, latchEnd);
latchStart.set(new CountDownLatch(show ? 1 : 2));
latchEnd.set(new CountDownLatch(2));
// For measuring hide, lets show IME first.
if (!show) {
activity.runOnUiThread(() -> {
controller.show(WindowInsets.Type.ime());
});
PollingCheck.check("IME show animation should finish ", TIMEOUT_1_S_IN_MS,
() -> latchStart.get().getCount() == 1
&& latchEnd.get().getCount() == 1);
}
if (!mIsTraceStarted && !state.isWarmingUp()) {
startAsyncAtrace();
mIsTraceStarted = true;
}
AtomicLong startTime = new AtomicLong();
activity.runOnUiThread(() -> {
startTime.set(SystemClock.elapsedRealtimeNanos());
if (show) {
controller.show(WindowInsets.Type.ime());
} else {
controller.hide(WindowInsets.Type.ime());
}
});
measuredTimeNs = waitForAnimationStart(latchStart, startTime);
// hide IME before next iteration.
if (show) {
activity.runOnUiThread(() -> controller.hide(WindowInsets.Type.ime()));
try {
latchEnd.get().await(TIMEOUT_1_S_IN_MS * 5, TimeUnit.MILLISECONDS);
if (latchEnd.get().getCount() != 0) {
Assert.fail("IME hide animation should finish.");
}
} catch (InterruptedException e) {
}
}
}
} finally {
if (mIsTraceStarted) {
stopAsyncAtrace();
}
}
mActivityRule.finishActivity();
addResultToState(state);
}
private long waitForAnimationStart(
AtomicReference<CountDownLatch> latchStart, AtomicLong startTime) {
try {
latchStart.get().await(TIMEOUT_1_S_IN_MS * 5, TimeUnit.MILLISECONDS);
if (latchStart.get().getCount() != 0) {
Assert.fail("IME animation should start " + latchStart.get().getCount());
}
} catch (InterruptedException e) { }
return SystemClock.elapsedRealtimeNanos() - startTime.get();
}
private void addResultToState(ManualBenchmarkState state) {
mTraceMethods.forAllSlices((key, slices) -> {
for (TraceMarkSlice slice : slices) {
state.addExtraResult(key, (long) (slice.getDurationInSeconds() * NANOS_PER_S));
}
});
Log.i(TAG, String.valueOf(mTraceMethods));
}
private Activity getActivityWithFocus() throws Exception {
final Activity activity = mActivityRule.launchActivity();
PollingCheck.check("Activity onResume()", TIMEOUT_1_S_IN_MS,
() -> activity.isResumed());
View editor = activity.findViewById(ID_EDITOR);
editor.requestFocus();
// wait till editor is focused so we don't count activity/view latency.
PollingCheck.check("Editor is focused", TIMEOUT_1_S_IN_MS,
() -> editor.isFocused());
getInstrumentation().waitForIdleSync();
return activity;
}
private void setImeListener(Activity activity,
@NonNull AtomicReference<CountDownLatch> latchStart,
@Nullable AtomicReference<CountDownLatch> latchEnd) {
// set IME animation listener
activity.getWindow().getDecorView().setWindowInsetsAnimationCallback(
new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
@NonNull
@Override
public WindowInsetsAnimation.Bounds onStart(
@NonNull WindowInsetsAnimation animation,
@NonNull WindowInsetsAnimation.Bounds bounds) {
latchStart.get().countDown();
return super.onStart(animation, bounds);
}
@NonNull
@Override
public WindowInsets onProgress(@NonNull WindowInsets insets,
@NonNull List<WindowInsetsAnimation> runningAnimations) {
return insets;
}
@Override
public void onEnd(@NonNull WindowInsetsAnimation animation) {
super.onEnd(animation);
if (latchEnd != null) {
latchEnd.get().countDown();
}
}
});
}
private void startAsyncAtrace() throws IOException {
mIsTraceStarted = true;
// IMF uses 'wm' component for trace in InputMethodService, InputMethodManagerService,
// WindowManagerService and 'view' for client window (InsetsController).
// TODO(b/167947940): Consider a separate input_method atrace
UI_AUTOMATION.executeShellCommand("atrace -b 32768 --async_start wm view");
// Avoid atrace isn't ready immediately.
SystemClock.sleep(TimeUnit.NANOSECONDS.toMillis(TIME_1_S_IN_NS));
}
private void stopAsyncAtrace() {
if (!mIsTraceStarted) {
return;
}
final ParcelFileDescriptor pfd = UI_AUTOMATION.executeShellCommand("atrace --async_stop");
mIsTraceStarted = false;
final InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = reader.readLine()) != null) {
mTraceMethods.visit(line);
}
} catch (IOException e) {
Log.w(TAG, "Failed to read the result of stopped atrace", e);
}
}
@Override
public void onStart(int iteration) {
// Do not capture trace when profiling because the result will be much slower.
stopAsyncAtrace();
}
@Override
public void onFinished(int iteration) {
// do nothing.
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright (C) 2020 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.inputmethod;
import static android.perftests.utils.PerfTestActivity.INTENT_EXTRA_ADD_EDIT_TEXT;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import android.app.KeyguardManager;
import android.app.UiAutomation;
import android.content.Context;
import android.content.Intent;
import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.perftests.utils.PerfTestActivity;
import androidx.test.rule.ActivityTestRule;
import org.junit.BeforeClass;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Objects;
import java.util.function.Consumer;
public class ImePerfTestBase {
static final UiAutomation UI_AUTOMATION = getInstrumentation().getUiAutomation();
static final long NANOS_PER_S = 1000L * 1000 * 1000;
static final long TIME_1_S_IN_NS = 1 * NANOS_PER_S;
static final long TIMEOUT_1_S_IN_MS = 1 * 1000L;
@BeforeClass
public static void setUpOnce() {
final Context context = getInstrumentation().getContext();
if (!context.getSystemService(PowerManager.class).isInteractive()
|| context.getSystemService(KeyguardManager.class).isKeyguardLocked()) {
executeShellCommand("input keyevent KEYCODE_WAKEUP");
executeShellCommand("wm dismiss-keyguard");
}
context.startActivity(new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
/**
* Executes shell command with reading the output. It may also used to block until the current
* command is completed.
*/
static ByteArrayOutputStream executeShellCommand(String command) {
final ParcelFileDescriptor pfd = UI_AUTOMATION.executeShellCommand(command);
final byte[] buf = new byte[512];
final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
int bytesRead;
try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
while ((bytesRead = fis.read(buf)) != -1) {
bytes.write(buf, 0, bytesRead);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return bytes;
}
/** Returns how many iterations should run with method tracing. */
static int getProfilingIterations() {
return ImePerfRunPrecondition.sProfilingIterations;
}
static void runWithShellPermissionIdentity(Runnable runnable) {
UI_AUTOMATION.adoptShellPermissionIdentity();
try {
runnable.run();
} finally {
UI_AUTOMATION.dropShellPermissionIdentity();
}
}
static class SettingsSession<T> implements AutoCloseable {
private final Consumer<T> mSetter;
private final T mOriginalValue;
private boolean mChanged;
SettingsSession(T originalValue, Consumer<T> setter) {
mOriginalValue = originalValue;
mSetter = setter;
}
void set(T value) {
if (Objects.equals(value, mOriginalValue)) {
mChanged = false;
return;
}
mSetter.accept(value);
mChanged = true;
}
@Override
public void close() {
if (mChanged) {
mSetter.accept(mOriginalValue);
}
}
}
/**
* 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(getInstrumentation().getTargetContext(), PerfTestActivity.class);
PerfTestActivityRule() {
this(false /* launchActivity */);
}
PerfTestActivityRule(boolean launchActivity) {
super(PerfTestActivity.class, false /* initialTouchMode */, launchActivity);
}
@Override
protected Intent getActivityIntent() {
return mStartIntent;
}
@Override
public PerfTestActivity launchActivity(Intent intent) {
intent.putExtra(INTENT_EXTRA_ADD_EDIT_TEXT, true);
return super.launchActivity(intent);
}
PerfTestActivity launchActivity() {
return launchActivity(mStartIntent);
}
}
static String[] buildArray(String[]... arrays) {
int length = 0;
for (String[] array : arrays) {
length += array.length;
}
String[] newArray = new String[length];
int offset = 0;
for (String[] array : arrays) {
System.arraycopy(array, 0, newArray, offset, array.length);
offset += array.length;
}
return newArray;
}
}

View File

@@ -21,6 +21,8 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.LinearLayout;
/**
* A simple activity used for testing, e.g. performance of activity switching, or as a base
@@ -28,6 +30,8 @@ import android.view.WindowManager;
*/
public class PerfTestActivity extends Activity {
public static final String INTENT_EXTRA_KEEP_SCREEN_ON = "keep_screen_on";
public static final String INTENT_EXTRA_ADD_EDIT_TEXT = "add_edit_text";
public static final int ID_EDITOR = 3252356;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -35,6 +39,15 @@ public class PerfTestActivity extends Activity {
if (getIntent().getBooleanExtra(INTENT_EXTRA_KEEP_SCREEN_ON, false)) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
if (getIntent().getBooleanExtra(INTENT_EXTRA_ADD_EDIT_TEXT, false)) {
final LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
final EditText editText = new EditText(this);
editText.setId(ID_EDITOR);
layout.addView(editText);
setContentView(layout);
}
}
public static Intent createLaunchIntent(Context context) {