diff options
author | gopinath <gelanchezhian@google.com> | 2019-07-01 14:04:48 -0700 |
---|---|---|
committer | Gopinath Elanchezhian <gelanchezhian@google.com> | 2019-07-01 21:24:07 +0000 |
commit | 553fa190adebd397a7cc45e7a55eae94ed3b9d4b (patch) | |
tree | ffac64423810af98cf9a4b5f99e6c1d87d8a2a98 | |
parent | b1787ae462d2cbfe4570d948bf465400fc197813 (diff) | |
download | platform_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
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); + } +} |