Initial setup of a SystemMemoryTest.
Set up a memory test to experiment with actionable memory metrics and
detect regressions in system server memory use.
The initial CUJ is to launch an instrumentation that does nothing for
a few seconds.
The initial metric is to read showmap for system_server and report
VSS, RSS, and PSS.
The CUJ and metrics will be made more interesting once the basic
infrastructure for continuously running the test is set up.
Bug: 111830582
Test: tradefed.sh run commandAndExit template/local_min --template:map test=system-memory-test
Change-Id: I8793adb66de4adab254173585e2c8afc754d4a3a
diff --git a/tests/SystemMemoryTest/Android.mk b/tests/SystemMemoryTest/Android.mk
new file mode 100644
index 0000000..09a1618
--- /dev/null
+++ b/tests/SystemMemoryTest/Android.mk
@@ -0,0 +1,17 @@
+# Copyright (C) 2018 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(call all-subdir-makefiles)
diff --git a/tests/SystemMemoryTest/README.txt b/tests/SystemMemoryTest/README.txt
new file mode 100644
index 0000000..de5042c
--- /dev/null
+++ b/tests/SystemMemoryTest/README.txt
@@ -0,0 +1,21 @@
+This directory contains a test for system server memory use.
+
+Directory structure
+===================
+device
+ - those parts of the test that run on device.
+
+host
+ - those parts of the test that run on host.
+
+Running the test
+================
+
+You can manually run the test as follows:
+
+ make tradefed-all system-memory-test SystemMemoryTestDevice
+ tradefed.sh run commandAndExit template/local_min --template:map test=system-memory-test
+
+This installs and runs the test on device. You can see the metrics in the
+tradefed output.
+
diff --git a/tests/SystemMemoryTest/device/Android.mk b/tests/SystemMemoryTest/device/Android.mk
new file mode 100644
index 0000000..75408df
--- /dev/null
+++ b/tests/SystemMemoryTest/device/Android.mk
@@ -0,0 +1,24 @@
+# Copyright (C) 2018 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.
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_PACKAGE_NAME := SystemMemoryTestDevice
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_COMPATIBILITY_SUITE := general-tests
+include $(BUILD_PACKAGE)
diff --git a/tests/SystemMemoryTest/device/AndroidManifest.xml b/tests/SystemMemoryTest/device/AndroidManifest.xml
new file mode 100644
index 0000000..e5e7844f
--- /dev/null
+++ b/tests/SystemMemoryTest/device/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.tests.sysmem.device">
+
+ <uses-sdk android:minSdkVersion="19" />
+
+ <instrumentation
+ android:name="com.android.tests.sysmem.device.Cujs"
+ android:targetPackage="com.android.tests.sysmem.device" />
+
+ <application
+ android:allowBackup="false"
+ android:label="System Memory Test">
+ </application>
+</manifest>
diff --git a/tests/SystemMemoryTest/device/src/com/android/tests/sysmem/device/Cujs.java b/tests/SystemMemoryTest/device/src/com/android/tests/sysmem/device/Cujs.java
new file mode 100644
index 0000000..6c0c593
--- /dev/null
+++ b/tests/SystemMemoryTest/device/src/com/android/tests/sysmem/device/Cujs.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.sysmem.device;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Critical user journeys used to exercise the system for test, driven from
+ * the device.
+ */
+public class Cujs extends Instrumentation {
+
+ private static final String TAG = "SystemMemoryTest";
+
+ @Override
+ public void onCreate(Bundle arguments) {
+ start();
+ }
+
+ @Override
+ public void onStart() {
+ // TODO: Exercise the system in more interesting ways.
+ // Mostly what matters is that whatever we do here is sustainable: it
+ // can be repeated indefinitely on the system without causing
+ // problems. For example, launching activities is fine. Installing
+ // applications is only fine if we also uninstall them as part of the
+ // test.
+ try {
+ Log.i(TAG, "Sleeping for 10 seconds...");
+ Thread.sleep(10 * 1000);
+ } catch (InterruptedException ignored) {
+ }
+
+ finish(Activity.RESULT_OK, null);
+ }
+}
diff --git a/tests/SystemMemoryTest/host/Android.mk b/tests/SystemMemoryTest/host/Android.mk
new file mode 100644
index 0000000..a516e38
--- /dev/null
+++ b/tests/SystemMemoryTest/host/Android.mk
@@ -0,0 +1,23 @@
+# Copyright (C) 2018 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_MODULE := system-memory-test
+LOCAL_MODULE_TAGS := optional
+LOCAL_JAVA_LIBRARIES := tradefed
+LOCAL_COMPATIBILITY_SUITE := general-tests
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tests/SystemMemoryTest/host/AndroidTest.xml b/tests/SystemMemoryTest/host/AndroidTest.xml
new file mode 100644
index 0000000..6d2c95f
--- /dev/null
+++ b/tests/SystemMemoryTest/host/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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 the system memory tests">
+ <option name="test-suite-tag" value="system-memory-test" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="SystemMemoryTestDevice.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class" value="com.android.tests.sysmem.host.MemoryTest" />
+ </test>
+</configuration>
diff --git a/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/Cujs.java b/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/Cujs.java
new file mode 100644
index 0000000..579d972
--- /dev/null
+++ b/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/Cujs.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tests.sysmem.host;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+/**
+ * Critical user journeys with which to exercise the system, driven from the
+ * host.
+ */
+public class Cujs {
+ private ITestDevice device;
+
+ public Cujs(ITestDevice device) {
+ this.device = device;
+ }
+
+ /**
+ * Runs the critical user journeys.
+ */
+ public void run() throws DeviceNotAvailableException {
+ // Invoke the Device Cujs instrumentation to run the cujs.
+ // TODO: Consider exercising the system in other interesting ways as
+ // well.
+ String command = "am instrument -w com.android.tests.sysmem.device/.Cujs";
+ device.executeShellCommand(command);
+ }
+}
diff --git a/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/MemoryTest.java b/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/MemoryTest.java
new file mode 100644
index 0000000..bbec065
--- /dev/null
+++ b/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/MemoryTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tests.sysmem.host;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
+import com.android.tradefed.testtype.IDeviceTest;
+import java.io.IOException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class MemoryTest implements IDeviceTest {
+
+ @Rule public TestMetrics testMetrics = new TestMetrics();
+ @Rule public TestLogData testLogs = new TestLogData();
+
+ private ITestDevice testDevice;
+ private int iterations = 0; // Number of times cujs have been run.
+ private Metrics metrics;
+ private Cujs cujs;
+
+ @Override
+ public void setDevice(ITestDevice device) {
+ testDevice = device;
+ metrics = new Metrics(device, testMetrics, testLogs);
+ cujs = new Cujs(device);
+ }
+
+ @Override
+ public ITestDevice getDevice() {
+ return testDevice;
+ }
+
+ // Invoke a single iteration of running the cujs.
+ private void runCujs() throws DeviceNotAvailableException {
+ cujs.run();
+ iterations++;
+ }
+
+ // Sample desired memory.
+ private void sample()
+ throws DeviceNotAvailableException, IOException, Metrics.MetricsException {
+ metrics.sample(String.format("%03d", iterations));
+ }
+
+ @Test
+ public void run() throws Exception {
+ sample(); // Sample before running cujs
+ runCujs();
+ sample(); // Sample after first iteration of cujs
+
+ // Run cujs in a loop to highlight memory leaks.
+ for (int i = 0; i < 5; ++i) {
+ for (int j = 0; j < 5; j++) {
+ runCujs();
+ }
+ sample();
+ }
+ }
+}
diff --git a/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/Metrics.java b/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/Metrics.java
new file mode 100644
index 0000000..7de092a
--- /dev/null
+++ b/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/Metrics.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.tests.sysmem.host;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.result.FileInputStreamSource;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.InputMismatchException;
+import java.util.Scanner;
+
+/**
+ * Utilities for sampling and reporting memory metrics.
+ */
+class Metrics {
+
+ private ITestDevice device;
+ private TestMetrics metrics;
+ private TestLogData logs;
+
+ /**
+ * Exception thrown in case of error sampling metrics.
+ */
+ public static class MetricsException extends Exception {
+ public MetricsException(String msg) {
+ super(msg);
+ }
+
+ MetricsException(String msg, Exception cause) {
+ super(msg, cause);
+ }
+ }
+
+ /**
+ * Constructs a metrics instance that will output high level metrics and
+ * more detailed breakdowns using the given <code>metrics</code> and
+ * <code>logs</code> objects.
+ *
+ * @param device the device to sample metrics from
+ * @param metrics where to log the high level metrics when taking a sample
+ * @param logs where to log detailed breakdowns when taking a sample
+ */
+ public Metrics(ITestDevice device, TestMetrics metrics, TestLogData logs) {
+ this.device = device;
+ this.metrics = metrics;
+ this.logs = logs;
+ }
+
+ /**
+ * Returns the pid for the process with the given name.
+ */
+ private int getPidForProcess(String name)
+ throws DeviceNotAvailableException, IOException, MetricsException {
+ String psout = device.executeShellCommand("ps -A -o PID,CMD");
+ Scanner sc = new Scanner(psout);
+ try {
+ // ps output is of the form:
+ // PID CMD
+ // 1 init
+ // 2 kthreadd
+ // ...
+ // 9693 ps
+ sc.nextLine();
+ while (sc.hasNextLine()) {
+ int pid = sc.nextInt();
+ String cmd = sc.next();
+
+ if (name.equals(cmd)) {
+ return pid;
+ }
+ }
+ } catch (InputMismatchException e) {
+ throw new MetricsException("unexpected ps output format: " + psout, e);
+ }
+
+ throw new MetricsException("failed to get pid for process " + name);
+ }
+
+ /**
+ * Samples the current memory use on the system. Outputs high level test
+ * metrics and detailed breakdowns to the TestMetrics and TestLogData
+ * objects provided when constructing this Metrics instance. The metrics
+ * and log names are prefixed with the given label.
+ *
+ * @param label prefix to use for metrics and logs output for this sample.
+ */
+ void sample(String label) throws DeviceNotAvailableException, IOException, MetricsException {
+ // adb root access is required to get showmap
+ device.enableAdbRoot();
+
+ int pid = getPidForProcess("system_server");
+
+ // Read showmap for system server and add it as a test log
+ String showmap = device.executeShellCommand("showmap " + pid);
+ String showmapLabel = label + ".system_server.showmap";
+ File file = File.createTempFile(showmapLabel, "txt");
+ PrintStream ps = new PrintStream(file);
+ ps.print(showmap);
+ try (FileInputStreamSource dataStream = new FileInputStreamSource(file)) {
+ logs.addTestLog(showmapLabel, LogDataType.TEXT, dataStream);
+ }
+
+ // Extract VSS, PSS and RSS from the showmap and output them as metrics.
+ // The last lines of the showmap output looks something like:
+ // virtual shared shared private private
+ // size RSS PSS clean dirty clean dirty swap swapPSS # object
+ //-------- -------- -------- -------- -------- -------- -------- -------- -------- ---- ------------------------------
+ // 928480 113016 24860 87348 7916 3632 14120 1968 1968 1900 TOTAL
+ try {
+ int pos = showmap.lastIndexOf("----");
+ Scanner sc = new Scanner(showmap.substring(pos));
+ sc.next();
+ long vss = sc.nextLong();
+ long rss = sc.nextLong();
+ long pss = sc.nextLong();
+
+ metrics.addTestMetric(String.format("%s.system_server.vss", label), Long.toString(vss));
+ metrics.addTestMetric(String.format("%s.system_server.rss", label), Long.toString(rss));
+ metrics.addTestMetric(String.format("%s.system_server.pss", label), Long.toString(pss));
+ } catch (InputMismatchException e) {
+ throw new MetricsException("unexpected showmap format", e);
+ }
+
+ // TODO: Experiment with other additional metrics.
+
+ // TODO: Consider launching an instrumentation to collect metrics from
+ // within the device itself.
+ }
+}