summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgopinath <gelanchezhian@google.com>2019-07-01 14:04:48 -0700
committerGopinath Elanchezhian <gelanchezhian@google.com>2019-07-01 21:24:07 +0000
commit553fa190adebd397a7cc45e7a55eae94ed3b9d4b (patch)
treeffac64423810af98cf9a4b5f99e6c1d87d8a2a98
parentb1787ae462d2cbfe4570d948bf465400fc197813 (diff)
downloadplatform_testing-553fa190adebd397a7cc45e7a55eae94ed3b9d4b.tar.gz
Backport totalpss listener to qt-dev.
Bug: b/132610587 Test: TotalPssHelperTest, TotalPssMetricListenerTest Change-Id: I1c8969c7b2ec8db9e81e1b50fc9addc1d3582245 Merged-In: Iae351ecaf32fbdbf555e95d01d9252a65e68ec34 Merged-In: I92fad6a694266d0872dd0e2c4973f3c2351e8847
-rw-r--r--libraries/collectors-helper/memory/src/com/android/helpers/TotalPssHelper.java218
-rw-r--r--libraries/collectors-helper/memory/test/src/com/android/helpers/tests/TotalPssHelperTest.java105
-rw-r--r--libraries/device-collectors/src/main/java/android/device/collectors/TotalPssMetricListener.java93
-rw-r--r--libraries/device-collectors/src/test/java/android/device/collectors/TotalPssMetricListenerTest.java100
4 files changed, 516 insertions, 0 deletions
diff --git a/libraries/collectors-helper/memory/src/com/android/helpers/TotalPssHelper.java b/libraries/collectors-helper/memory/src/com/android/helpers/TotalPssHelper.java
new file mode 100644
index 000000000..4e20c74ce
--- /dev/null
+++ b/libraries/collectors-helper/memory/src/com/android/helpers/TotalPssHelper.java
@@ -0,0 +1,218 @@
+/*
+ * 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 com.android.helpers;
+
+import static com.android.helpers.MetricUtility.constructKey;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.content.Context;
+import android.os.Debug.MemoryInfo;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper to collect totalpss memory usage per process tracked by the ActivityManager
+ * memoryinfo.
+ */
+public class TotalPssHelper implements ICollectorHelper<Long> {
+
+ private static final String TAG = TotalPssHelper.class.getSimpleName();
+
+ private static final int DEFAULT_THRESHOLD = 1024;
+ private static final int DEFAULT_MIN_ITERATIONS = 6;
+ private static final int DEFAULT_MAX_ITERATIONS = 20;
+ private static final int DEFAULT_SLEEP_TIME = 1000;
+ private static final String PSS_METRIC_PREFIX = "AM_TOTAL_PSS";
+
+ private String[] mProcessNames;
+ // Minimum number of iterations needed before deciding on the memory usage.
+ private int mMinIterations;
+ // Maximum number of iterations needed waiting for memory usage to be stabilized.
+ private int mMaxIterations;
+ // Sleep time in between the iterations.
+ private int mSleepTime;
+ // Threshold in kb to use whether the data is stabilized.
+ private int mThreshold;
+ // Map to maintain the pss memory size.
+ private Map<String, Long> mPssFinalMap = new HashMap<>();
+
+ public void setUp(String... processNames) {
+ mProcessNames = processNames;
+ // Minimum iterations should be atleast 3 to check for the
+ // stabilization of the memory usage.
+ mMinIterations = DEFAULT_MIN_ITERATIONS;
+ mMaxIterations = DEFAULT_MAX_ITERATIONS;
+ mSleepTime = DEFAULT_SLEEP_TIME;
+ mThreshold = DEFAULT_THRESHOLD;
+ }
+
+ @Override
+ public boolean startCollecting() {
+ return true;
+ }
+
+ @Override
+ public Map<String, Long> getMetrics() {
+ if (mMinIterations < 3) {
+ Log.w(TAG, "Need atleast 3 iterations to check memory usage stabilization.");
+ return mPssFinalMap;
+ }
+ if (mProcessNames != null) {
+ for (String processName : mProcessNames) {
+ if (!processName.isEmpty()) {
+ measureMemory(processName);
+ }
+ }
+ }
+ return mPssFinalMap;
+ }
+
+ @Override
+ public boolean stopCollecting() {
+ return true;
+ }
+
+ /**
+ * Measure memory info of the given process name tracked by the activity manager
+ * MemoryInfo(i.e getTotalPss).
+ *
+ * @param processName to calculate the memory info.
+ */
+ private void measureMemory(String processName) {
+ Log.i(TAG, "Tracking memory usage of the process - " + processName);
+ List<Long> pssData = new ArrayList<Long>();
+ long pss = 0;
+ int iteration = 0;
+ while (iteration < mMaxIterations) {
+ sleep(mSleepTime);
+ pss = getPss(processName);
+ pssData.add(pss);
+ if (iteration >= mMinIterations && stabilized(pssData)) {
+ Log.i(TAG, "Memory usage stabilized at iteration count = " + iteration);
+ mPssFinalMap.put(constructKey(PSS_METRIC_PREFIX, processName), pss);
+ return;
+ }
+ iteration++;
+ }
+
+ Log.i(TAG, processName + " memory usage did not stabilize."
+ + " Returning the average of the pss data collected.");
+ mPssFinalMap.put(constructKey(PSS_METRIC_PREFIX, processName), average(pssData));
+ }
+
+ /**
+ * Time to sleep in between the iterations.
+ *
+ * @param time in ms to sleep.
+ */
+ private void sleep(int time) {
+ try {
+ Thread.sleep(time);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+
+ /**
+ * Get the total pss memory of the given process name.
+ *
+ * @param processName of the process to measure the memory.
+ * @return the memory in KB.
+ */
+ private long getPss(String processName) {
+ ActivityManager am = (ActivityManager) InstrumentationRegistry.getInstrumentation()
+ .getContext().getSystemService(Context.ACTIVITY_SERVICE);
+ List<RunningAppProcessInfo> apps = am.getRunningAppProcesses();
+ for (RunningAppProcessInfo proc : apps) {
+ if (!proc.processName.equals(processName)) {
+ continue;
+ }
+ MemoryInfo meminfo = am.getProcessMemoryInfo(new int[] {
+ proc.pid
+ })[0];
+ Log.i(TAG,
+ String.format("Memory usage of process - %s is %d", processName,
+ meminfo.getTotalPss()));
+ return meminfo.getTotalPss();
+ }
+ Log.w(TAG, "Not able to find the process id for the process = " + processName);
+ return 0;
+ }
+
+ /**
+ * Checks whether the memory usage is stabilized by calculating the sum of the difference
+ * between the last 3 values and comparing that to the threshold.
+ *
+ * @param pssData list of pssData of the given process name.
+ * @return true if the memory is stabilized.
+ */
+ private boolean stabilized(List<Long> pssData) {
+ long diff1 = Math.abs(pssData.get(pssData.size() - 1) - pssData.get(pssData.size() - 2));
+ long diff2 = Math.abs(pssData.get(pssData.size() - 2) - pssData.get(pssData.size() - 3));
+ Log.i(TAG, "diff1=" + diff1 + " diff2=" + diff2);
+ return (diff1 + diff2) < mThreshold;
+ }
+
+ /**
+ * Returns the average of the pssData collected for the maxIterations.
+ *
+ * @param pssData list of pssData.
+ * @return
+ */
+ private long average(List<Long> pssData) {
+ long sum = 0;
+ for (long sample : pssData) {
+ sum += sample;
+ }
+ return sum / pssData.size();
+ }
+
+ /**
+ * @param minIterations before starting to check for memory is stabilized.
+ */
+ public void setMinIterations(int minIterations) {
+ mMinIterations = minIterations;
+ }
+
+ /**
+ * @param maxIterations to wait for memory to be stabilized.
+ */
+ public void setMaxIterations(int maxIterations) {
+ mMaxIterations = maxIterations;
+ }
+
+ /**
+ * @param sleepTime in between the iterations.
+ */
+ public void setSleepTime(int sleepTime) {
+ mSleepTime = sleepTime;
+ }
+
+ /**
+ * @param threshold for difference in memory usage between two successive iterations in kb
+ */
+ public void setThreshold(int threshold) {
+ mThreshold = threshold;
+ }
+}
diff --git a/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/TotalPssHelperTest.java b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/TotalPssHelperTest.java
new file mode 100644
index 000000000..b3dd138a7
--- /dev/null
+++ b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/TotalPssHelperTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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 com.android.helpers.tests;
+
+import static com.android.helpers.MetricUtility.constructKey;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.helpers.TotalPssHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+/**
+ * Android Unit tests for {@link TotalPssHelper}.
+ *
+ * To run:
+ * atest CollectorsHelperTest:com.android.helpers.tests.TotalPssHelperTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class TotalPssHelperTest {
+
+ // Process name used for testing
+ private static final String TEST_PROCESS_NAME = "com.android.systemui";
+ // Second process name used for testing
+ private static final String TEST_PROCESS_NAME_2 = "com.google.android.apps.nexuslauncher";
+ // Second process name used for testing
+ private static final String INVALID_PROCESS_NAME = "abc";
+ // Pss prefix in Key.
+ private static final String PSS_METRIC_PREFIX = "AM_TOTAL_PSS";
+
+ private TotalPssHelper mTotalPssHelper;
+
+ @Before
+ public void setUp() {
+ mTotalPssHelper = new TotalPssHelper();
+ }
+
+ /** Test no metrics are sampled if process name is empty. */
+ @Test
+ public void testEmptyProcessName() {
+ mTotalPssHelper.setUp("");
+ Map<String, Long> pssMetrics = mTotalPssHelper.getMetrics();
+ assertTrue(pssMetrics.isEmpty());
+ }
+
+ /** Test no metrics are sampled if process names is null */
+ @Test
+ public void testNullProcessName() {
+ mTotalPssHelper.setUp(null);
+ Map<String, Long> pssMetrics = mTotalPssHelper.getMetrics();
+ assertTrue(pssMetrics.isEmpty());
+ }
+
+ /** Test getting metrics for single process. */
+ @Test
+ public void testGetMetrics_OneProcess() {
+ mTotalPssHelper.setUp(TEST_PROCESS_NAME);
+ Map<String, Long> pssMetrics = mTotalPssHelper.getMetrics();
+ assertFalse(pssMetrics.isEmpty());
+ assertTrue(pssMetrics.containsKey(constructKey(PSS_METRIC_PREFIX, TEST_PROCESS_NAME)));
+ assertTrue(pssMetrics.get(constructKey(PSS_METRIC_PREFIX, TEST_PROCESS_NAME)) > 0);
+ }
+
+ /** Test getting metrics for multiple process. */
+ @Test
+ public void testGetMetrics_MultipleProcesses() {
+ mTotalPssHelper.setUp(TEST_PROCESS_NAME, TEST_PROCESS_NAME_2);
+ Map<String, Long> pssMetrics = mTotalPssHelper.getMetrics();
+ assertFalse(pssMetrics.isEmpty());
+ assertTrue(pssMetrics.containsKey(constructKey(PSS_METRIC_PREFIX, TEST_PROCESS_NAME)));
+ assertTrue(pssMetrics.containsKey(constructKey(PSS_METRIC_PREFIX, TEST_PROCESS_NAME_2)));
+ assertTrue(pssMetrics.get(constructKey(PSS_METRIC_PREFIX, TEST_PROCESS_NAME)) > 0);
+ assertTrue(pssMetrics.get(constructKey(PSS_METRIC_PREFIX, TEST_PROCESS_NAME_2)) > 0);
+ }
+
+ /** Test pss metric is 0 for invalid process name. */
+ @Test
+ public void testGetMetrics_InvalidProcess() {
+ mTotalPssHelper.setUp(INVALID_PROCESS_NAME);
+ Map<String, Long> pssMetrics = mTotalPssHelper.getMetrics();
+ assertTrue(pssMetrics.containsKey(constructKey(PSS_METRIC_PREFIX, INVALID_PROCESS_NAME)));
+ assertTrue(pssMetrics.get(constructKey(PSS_METRIC_PREFIX, INVALID_PROCESS_NAME)) == 0);
+ }
+}
diff --git a/libraries/device-collectors/src/main/java/android/device/collectors/TotalPssMetricListener.java b/libraries/device-collectors/src/main/java/android/device/collectors/TotalPssMetricListener.java
new file mode 100644
index 000000000..bb5a8a9e0
--- /dev/null
+++ b/libraries/device-collectors/src/main/java/android/device/collectors/TotalPssMetricListener.java
@@ -0,0 +1,93 @@
+/*
+ * 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.device.collectors;
+
+import android.device.collectors.annotations.OptionClass;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.helpers.TotalPssHelper;
+
+/**
+ * A {@link TotalPssMetricListener} that measures process total pss tracked per
+ * process in activity manaager.
+ *
+ * Options:
+ * -e process-names [processName] : the process from the test case that we want to
+ * measure memory for.
+ */
+@OptionClass(alias = "totalpss-collector")
+public class TotalPssMetricListener extends BaseCollectionListener<Long> {
+
+ private static final String TAG = TotalPssMetricListener.class.getSimpleName();
+ @VisibleForTesting static final String PROCESS_SEPARATOR = ",";
+ @VisibleForTesting static final String PROCESS_NAMES_KEY = "process-names";
+ @VisibleForTesting static final String MIN_ITERATIONS_KEY = "min_iterations";
+ @VisibleForTesting static final String MAX_ITERATIONS_KEY = "max_iterations";
+ @VisibleForTesting static final String SLEEP_TIME_KEY = "sleep_time_ms";
+ @VisibleForTesting static final String THRESHOLD_KEY = "threshold_kb";
+ private TotalPssHelper mTotalPssHelper = new TotalPssHelper();
+
+ public TotalPssMetricListener() {
+ createHelperInstance(mTotalPssHelper);
+ }
+
+ /**
+ * Constructor to simulate receiving the instrumentation arguments. Should not be used except
+ * for testing.
+ */
+ @VisibleForTesting
+ public TotalPssMetricListener(Bundle args, TotalPssHelper helper) {
+ super(args, helper);
+ mTotalPssHelper = helper;
+ createHelperInstance(mTotalPssHelper);
+ }
+
+ /**
+ * Adds the options for total pss collector.
+ */
+ @Override
+ public void setupAdditionalArgs() {
+ Bundle args = getArgsBundle();
+ String procsString = args.getString(PROCESS_NAMES_KEY);
+ if (procsString == null) {
+ Log.e(TAG, "No processes provided to sample");
+ return;
+ }
+
+ String[] procs = procsString.split(PROCESS_SEPARATOR);
+ mTotalPssHelper.setUp(procs);
+
+ if (args.getString(MIN_ITERATIONS_KEY) != null) {
+ mTotalPssHelper.setMinIterations(Integer.parseInt(args.getString(MIN_ITERATIONS_KEY)));
+ }
+
+ if (args.getString(MAX_ITERATIONS_KEY) != null) {
+ mTotalPssHelper.setMaxIterations(Integer.parseInt(args.getString(MAX_ITERATIONS_KEY)));
+ }
+
+ if (args.getString(SLEEP_TIME_KEY) != null) {
+ mTotalPssHelper.setSleepTime(Integer.parseInt(args.getString(SLEEP_TIME_KEY)));
+ }
+
+ if (args.getString(THRESHOLD_KEY) != null) {
+ mTotalPssHelper.setThreshold(Integer.parseInt(args.getString(THRESHOLD_KEY)));
+ }
+ }
+}
diff --git a/libraries/device-collectors/src/test/java/android/device/collectors/TotalPssMetricListenerTest.java b/libraries/device-collectors/src/test/java/android/device/collectors/TotalPssMetricListenerTest.java
new file mode 100644
index 000000000..665397089
--- /dev/null
+++ b/libraries/device-collectors/src/test/java/android/device/collectors/TotalPssMetricListenerTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.device.collectors;
+
+import static android.device.collectors.TotalPssMetricListener.PROCESS_NAMES_KEY;
+import static android.device.collectors.TotalPssMetricListener.PROCESS_SEPARATOR;
+import static android.device.collectors.TotalPssMetricListener.MIN_ITERATIONS_KEY;
+import static android.device.collectors.TotalPssMetricListener.MAX_ITERATIONS_KEY;
+import static android.device.collectors.TotalPssMetricListener.SLEEP_TIME_KEY;
+import static android.device.collectors.TotalPssMetricListener.THRESHOLD_KEY;
+
+import static org.mockito.Mockito.verify;
+
+import android.app.Instrumentation;
+import android.os.Bundle;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.helpers.TotalPssHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Android Unit tests for {@link TotalPssMetricListener}.
+ *
+ * To run:
+ * atest CollectorDeviceLibTest:android.device.collectors.TotalPssMetricListenerTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class TotalPssMetricListenerTest {
+
+ @Mock
+ private Instrumentation mInstrumentation;
+ @Mock
+ private TotalPssHelper mTotalPssMetricHelper;
+
+ private TotalPssMetricListener mListener;
+ private Description mRunDesc;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mRunDesc = Description.createSuiteDescription("run");
+ }
+
+ private TotalPssMetricListener initListener(Bundle b) {
+ TotalPssMetricListener listener = new TotalPssMetricListener(b, mTotalPssMetricHelper);
+ listener.setInstrumentation(mInstrumentation);
+ return listener;
+ }
+
+ @Test
+ public void testHelperReceivesProcessNames() throws Exception {
+ Bundle b = new Bundle();
+ b.putString(PROCESS_NAMES_KEY, "process1" + PROCESS_SEPARATOR + "process2");
+ mListener = initListener(b);
+
+ mListener.testRunStarted(mRunDesc);
+
+ verify(mTotalPssMetricHelper).setUp("process1", "process2");
+ }
+
+ @Test
+ public void testAdditionalPssOptions() throws Exception {
+ Bundle b = new Bundle();
+ b.putString(PROCESS_NAMES_KEY, "process1");
+ b.putString(MIN_ITERATIONS_KEY, "50");
+ b.putString(MAX_ITERATIONS_KEY, "102");
+ b.putString(SLEEP_TIME_KEY, "2000");
+ b.putString(THRESHOLD_KEY, "2048");
+ mListener = initListener(b);
+
+ mListener.testRunStarted(mRunDesc);
+
+ verify(mTotalPssMetricHelper).setUp("process1");
+ verify(mTotalPssMetricHelper).setMinIterations(50);
+ verify(mTotalPssMetricHelper).setMaxIterations(102);
+ verify(mTotalPssMetricHelper).setSleepTime(2000);
+ verify(mTotalPssMetricHelper).setThreshold(2048);
+ }
+}