Snap for 10075933 from f94feb634598b889847d1ac1cec474562067470f to mainline-mediaprovider-release
Change-Id: Ib1268622988aaba0220460d4a143591b7275d99e
diff --git a/libraries/collectors-helper/memory/src/com/android/helpers/ShowmapSnapshotHelper.java b/libraries/collectors-helper/memory/src/com/android/helpers/ShowmapSnapshotHelper.java
index 7587e6c..644cda0 100644
--- a/libraries/collectors-helper/memory/src/com/android/helpers/ShowmapSnapshotHelper.java
+++ b/libraries/collectors-helper/memory/src/com/android/helpers/ShowmapSnapshotHelper.java
@@ -19,12 +19,17 @@
import static com.android.helpers.MetricUtility.constructKey;
import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
import androidx.test.InstrumentationRegistry;
import androidx.test.uiautomator.UiDevice;
+
+import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.InputMismatchException;
@@ -49,7 +54,13 @@
public static final String ALL_PROCESSES_CMD = "ps -A";
private static final String SHOWMAP_CMD = "showmap -v %d";
private static final String CHILD_PROCESSES_CMD = "ps -A --ppid %d";
-
+ @VisibleForTesting public static final String OOM_SCORE_ADJ_CMD = "cat /proc/%d/oom_score_adj";
+ private static final String THREADS_FILE_PATH = "/sdcard/countThreads.sh";
+ @VisibleForTesting public static final String THREADS_CMD = "sh /sdcard/countThreads.sh";
+ private static final String THREADS_EXEC_SCRIPT =
+ "for i in $(ls /proc | grep -E [0-9]+); do echo \"threads_count_$(cat"
+ + " /proc/$i/cmdline) : $(ls /proc/$i/task | wc -l)\"; done;";
+ public static final String THREADS_PATTERN = "(?<key>^threads_count_.+) : (?<value>[0-9]+)";
public static final String OUTPUT_METRIC_PATTERN = "showmap_%s_bytes";
public static final String OUTPUT_FILE_PATH_KEY = "showmap_output_file";
public static final String PROCESS_COUNT = "process_count";
@@ -57,10 +68,11 @@
public static final String OUTPUT_CHILD_PROCESS_COUNT_KEY = CHILD_PROCESS_COUNT_PREFIX + "_%s";
public static final String PROCESS_WITH_CHILD_PROCESS_COUNT =
"process_with_child_process_count";
- private static final String CHILD_PROCESS_NAME_REGEX = "(\\S+)$";
private static final String METRIC_VALUE_SEPARATOR = "_";
public static final String PARENT_PROCESS_STRING = "parent_process";
public static final String CHILD_PROCESS_STRING = "child_process";
+ // The reason to skip the process: b/272181398#comment24
+ private static final Set<String> SKIP_PROCESS = new HashSet<>(Arrays.asList("logcat", "sh"));
private String[] mProcessNames = null;
private String mTestOutputDir = null;
@@ -70,6 +82,7 @@
private boolean mCollectForAllProcesses = false;
private UiDevice mUiDevice;
private boolean mRunGcPrecollection;
+ private boolean mRunCountThreads;
// Map to maintain per-process memory info
private Map<String, String> mMemoryMap = new HashMap<>();
@@ -79,11 +92,12 @@
private Map<String, List<Integer>> mMetricNameIndexMap = new HashMap<>();
public void setUp(String testOutputDir, String... processNames) {
- mProcessNames = processNames;
- mTestOutputDir = testOutputDir;
- mDropCacheOption = 0;
- mRunGcPrecollection = false;
- mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ mProcessNames = processNames;
+ mTestOutputDir = testOutputDir;
+ mDropCacheOption = 0;
+ mRunGcPrecollection = false;
+ mRunCountThreads = false;
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
}
@Override
@@ -133,11 +147,13 @@
@Override
public Map<String, String> getMetrics() {
try {
+ if (mRunCountThreads) {
+ mMemoryMap.putAll(execCountThreads());
+ }
// Drop cache if requested
if (mDropCacheOption > 0) {
dropCache(mDropCacheOption);
}
-
if (mCollectForAllProcesses) {
Log.i(TAG, "Collecting memory metrics for all processes.");
mProcessNames = getAllProcessNames();
@@ -148,7 +164,6 @@
return mMemoryMap;
}
HashSet<Integer> zygoteChildrenPids = getZygoteChildrenPids();
-
FileWriter writer = new FileWriter(new File(mTestOutputFile), true);
for (String processName : mProcessNames) {
List<Integer> pids = new ArrayList<>();
@@ -156,31 +171,37 @@
try {
pids = getPids(processName);
for (Integer pid : pids) {
- // Force Garbage collect to trim transient objects before taking memory
- // measurements as memory tests aim to track persistent memory regression
- // instead of transient memory which also allows for de-noising and reducing
- // likelihood of false alerts.
- if (mRunGcPrecollection && zygoteChildrenPids.contains(pid)) {
- // Skip native processes from sending GC signal.
- android.os.Trace.beginSection("IssueGCForPid: " + pid);
- // Perform a synchronous GC which happens when we request meminfo
- // This save us the need of setting up timeouts that may or may not
- // match with the end time of GC.
- mUiDevice.executeShellCommand("dumpsys meminfo -a " + pid);
- android.os.Trace.endSection();
- }
+ // Force Garbage collect to trim transient objects before taking memory
+ // measurements as memory tests aim to track persistent memory regression
+ // instead of transient memory which also allows for de-noising and reducing
+ // likelihood of false alerts.
+ if (mRunGcPrecollection && zygoteChildrenPids.contains(pid)) {
+ // Skip native processes from sending GC signal.
+ android.os.Trace.beginSection("IssueGCForPid: " + pid);
+ // Perform a synchronous GC which happens when we request meminfo
+ // This save us the need of setting up timeouts that may or may not
+ // match with the end time of GC.
+ mUiDevice.executeShellCommand("dumpsys meminfo -a " + pid);
+ android.os.Trace.endSection();
+ }
- android.os.Trace.beginSection("ExecuteShowmap");
- String showmapOutput = execShowMap(processName, pid);
- android.os.Trace.endSection();
- parseAndUpdateMemoryInfo(processName, showmapOutput);
- // Store showmap output into file. If there are more than one process
- // with same name write the individual showmap associated with pid.
- storeToFile(mTestOutputFile, processName, pid, showmapOutput, writer);
- // Parse number of child processes for the given pid and update the
- // total number of child process count for the process name that pid
- // is associated with.
- updateChildProcessesDetails(processName, pid);
+ // Skip the cached process for showmap and child process count
+ if (isCachedProcess(processName, pid)) {
+ Log.i(TAG, String.format("Skip the cached process %s.", processName));
+ continue;
+ }
+
+ android.os.Trace.beginSection("ExecuteShowmap");
+ String showmapOutput = execShowMap(processName, pid);
+ android.os.Trace.endSection();
+ parseAndUpdateMemoryInfo(processName, showmapOutput);
+ // Store showmap output into file. If there are more than one process
+ // with same name write the individual showmap associated with pid.
+ storeToFile(mTestOutputFile, processName, pid, showmapOutput, writer);
+ // Parse number of child processes for the given pid and update the
+ // total number of child process count for the process name that pid
+ // is associated with.
+ updateChildProcessesDetails(processName, pid);
}
} catch (RuntimeException e) {
Log.e(TAG, e.getMessage(), e.getCause());
@@ -206,7 +227,6 @@
} catch (IOException e) {
Log.e(TAG, String.format("Failed to write output file %s", mTestOutputFile), e);
}
-
return mMemoryMap;
}
@@ -223,8 +243,9 @@
String childrenCmdOutput = "";
try {
// Execute shell does not support shell substitution so it has to be executed twice.
- childrenCmdOutput = mUiDevice.executeShellCommand(
- "pgrep -P " + mUiDevice.executeShellCommand("pidof " + processName));
+ childrenCmdOutput =
+ mUiDevice.executeShellCommand(
+ "pgrep -P " + mUiDevice.executeShellCommand("pidof " + processName));
} catch (IOException e) {
Log.e(TAG, "Exception occurred reading children for process " + processName);
}
@@ -257,6 +278,15 @@
}
/**
+ * Sets option for counting the threads for all processes.
+ *
+ * @param shouldCountThreads whether it should run count threads
+ */
+ public void setCountThreadsOption(boolean shouldCountThreads) {
+ mRunCountThreads = shouldCountThreads;
+ }
+
+ /**
* Set drop cache option.
*
* @param dropCacheOption drop pagecache (1), slab (2) or all (3) cache
@@ -327,6 +357,41 @@
}
/**
+ * Executes counting threads command for the process.
+ *
+ * @param processName name of the process to run showmap for
+ * @param pid pid of the process to run showmap for
+ * @return the output of showmap command
+ */
+ private Map<String, String> execCountThreads() throws IOException {
+ String countOutput;
+ Map<String, String> countResults = new HashMap<>();
+ try {
+ File execTempFile = new File(THREADS_FILE_PATH);
+ execTempFile.setWritable(true);
+ execTempFile.setExecutable(true, /*ownersOnly*/ false);
+ String countThreadsScriptPath = execTempFile.getAbsolutePath();
+ BufferedWriter writer = new BufferedWriter(new FileWriter(countThreadsScriptPath));
+ writer.write(THREADS_EXEC_SCRIPT);
+ writer.close();
+ countOutput = executeShellCommand(THREADS_CMD);
+ Pattern pattern =
+ Pattern.compile(THREADS_PATTERN, Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
+ String[] lines = countOutput.split("\n");
+ for (String line : lines) {
+ Matcher matcher = pattern.matcher(line);
+ boolean matchFound = matcher.find();
+ if (matchFound) {
+ countResults.put(matcher.group(1), matcher.group(2));
+ }
+ }
+ return countResults;
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to execute counting threads command", e);
+ }
+ }
+
+ /**
* Extract memory metrics from showmap command output for the process with {@code processName}
* name.
*
@@ -417,6 +482,22 @@
Log.i(TAG, String.format("Metric Name index map size %s", mMetricNameIndexMap.size()));
}
+ /** Return true if the giving process is a cached process */
+ public boolean isCachedProcess(String processName, long pid) {
+ try {
+ String score = executeShellCommand(String.format(OOM_SCORE_ADJ_CMD, pid));
+ boolean result = Integer.parseInt(score.trim()) >= 900;
+ if (result) {
+ Log.i(TAG, String.format("The process %s with pid %d is cached", processName, pid));
+ }
+ return result;
+ } catch (IOException e) {
+ Log.e(TAG, String.format("Unable to get process oom_score_adj for %s", processName), e);
+ // We don't know the process is cached or not, still collect it
+ return false;
+ }
+ }
+
/**
* Retrieves the number of child processes for the given process id and updates the total
* process count and adds a child process metric for the process name that pid is associated
@@ -427,8 +508,8 @@
*/
private void updateChildProcessesDetails(String processName, long pid) {
String childProcessName;
+ String childPID;
String completeChildProcessMetric;
- Pattern childProcessPattern = Pattern.compile(CHILD_PROCESS_NAME_REGEX);
try {
Log.i(TAG,
String.format("Retrieving child processes count for process name: %s with"
@@ -436,38 +517,47 @@
String childProcessesStr = mUiDevice
.executeShellCommand(String.format(CHILD_PROCESSES_CMD, pid));
Log.i(TAG, String.format("Child processes cmd output: %s", childProcessesStr));
- String[] childProcessStrSplit = childProcessesStr.split("\\n");
- // To discard the header line in the command output.
- int childProcessCount = childProcessStrSplit.length - 1;
- String childCountMetricKey = String.format(OUTPUT_CHILD_PROCESS_COUNT_KEY, processName);
+ int childProcessCount = 0;
+ String[] childProcessStrSplit = childProcessesStr.split("\\n");
+ for (String line : childProcessStrSplit) {
+ // To discard the header line in the command output.
+ if (Objects.equals(line, childProcessStrSplit[0])) continue;
+ String[] childProcessSplit = line.trim().split("\\s+");
+ /**
+ * final metric will be of following format
+ * parent_process_<process>_child_process_<process>
+ * parent_process_zygote64_child_process_system_server
+ */
+ childPID = childProcessSplit[1];
+ childProcessName = childProcessSplit[8];
+ // Skip the logcat and sh processes in child process count
+ if (SKIP_PROCESS.contains(childProcessName)
+ || isCachedProcess(childProcessName, Long.parseLong(childPID))) {
+ Log.i(
+ TAG,
+ String.format(
+ "Skip the child process %s in the parent process %s.",
+ childProcessName, processName));
+ continue;
+ }
+ childProcessCount++;
+ completeChildProcessMetric =
+ String.join(
+ METRIC_VALUE_SEPARATOR,
+ PARENT_PROCESS_STRING,
+ processName,
+ CHILD_PROCESS_STRING,
+ childProcessName);
+ mMemoryMap.put(completeChildProcessMetric, "1");
+ }
+ String childCountMetricKey = String.format(OUTPUT_CHILD_PROCESS_COUNT_KEY, processName);
if (childProcessCount > 0) {
mMemoryMap.put(childCountMetricKey,
Long.toString(
Long.parseLong(mMemoryMap.getOrDefault(childCountMetricKey, "0"))
+ childProcessCount));
}
- for (String line : childProcessStrSplit) {
- // To discard the header line in the command output.
- if (Objects.equals(line, childProcessStrSplit[0])) continue;
- Matcher childProcessMatcher = childProcessPattern.matcher(line);
- if (childProcessMatcher.find()) {
- /**
- * final metric will be of following format
- * parent_process_<process>_child_process_<process>
- * parent_process_zygote64_child_process_system_server
- */
- childProcessName = childProcessMatcher.group(1);
- completeChildProcessMetric =
- String.join(
- METRIC_VALUE_SEPARATOR,
- PARENT_PROCESS_STRING,
- processName,
- CHILD_PROCESS_STRING,
- childProcessName);
- mMemoryMap.put(completeChildProcessMetric, "1");
- }
- }
} catch (IOException e) {
throw new RuntimeException("Unable to run child process command.", e);
}
@@ -513,4 +603,10 @@
}
return allProcessNames.toArray(new String[0]);
}
+
+ /* Execute a shell command and return its output. */
+ @VisibleForTesting
+ public String executeShellCommand(String command) throws IOException {
+ return mUiDevice.executeShellCommand(command);
+ }
}
diff --git a/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/ShowmapSnapshotHelperTest.java b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/ShowmapSnapshotHelperTest.java
index d2e7d5b..9adc31c 100644
--- a/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/ShowmapSnapshotHelperTest.java
+++ b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/ShowmapSnapshotHelperTest.java
@@ -13,30 +13,40 @@
* 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.fail;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.matches;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import androidx.test.runner.AndroidJUnit4;
+
import com.android.helpers.ShowmapSnapshotHelper;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
/**
* Android Unit tests for {@link ShowmapSnapshotHelper}.
*
- * To run:
- * atest CollectorsHelperTest:com.android.helpers.tests.ShowmapSnapshotHelperTest
+ * <p>To run: atest CollectorsHelperAospTest:ShowmapSnapshotHelperTest
*/
@RunWith(AndroidJUnit4.class)
public class ShowmapSnapshotHelperTest {
@@ -63,15 +73,25 @@
private static final String[] TWO_PROCESS_LIST = {
"com.android.systemui", "system_server"
};
+ private static final String[] MIXED_PROCESS_LIST = {
+ "com.android.systemui",
+ "system_server",
+ "com.google.android.googlequicksearchbox:search",
+ "android.process.acore"
+ };
+ private static final String[] CACHED_PROCESS_LIST = {
+ "com.google.android.googlequicksearchbox:search", "android.process.acore"
+ };
private static final String[] NO_PROCESS_LIST = {
null
};
- private ShowmapSnapshotHelper mShowmapSnapshotHelper;
+ private @Spy ShowmapSnapshotHelper mShowmapSnapshotHelper;
@Before
public void setUp() {
mShowmapSnapshotHelper = new ShowmapSnapshotHelper();
+ MockitoAnnotations.initMocks(this);
}
/**
@@ -146,6 +166,71 @@
testProcessList(METRIC_INDEX_STR, TWO_PROCESS_LIST);
}
+ /** Test that cached processes are skipped for showmap metrics. */
+ @Test
+ public void testGetMetrics_MixedProcess() {
+ doReturn(true)
+ .when(mShowmapSnapshotHelper)
+ .isCachedProcess(eq("com.google.android.googlequicksearchbox:search"), anyLong());
+ doReturn(true)
+ .when(mShowmapSnapshotHelper)
+ .isCachedProcess(eq("android.process.acore"), anyLong());
+ doReturn(false)
+ .when(mShowmapSnapshotHelper)
+ .isCachedProcess(eq("com.android.systemui"), anyLong());
+ doReturn(false)
+ .when(mShowmapSnapshotHelper)
+ .isCachedProcess(eq("system_server"), anyLong());
+ mShowmapSnapshotHelper.setUp(VALID_OUTPUT_DIR, MIXED_PROCESS_LIST);
+ mShowmapSnapshotHelper.setMetricNameIndex(METRIC_INDEX_STR);
+ assertTrue(mShowmapSnapshotHelper.startCollecting());
+ Map<String, String> metrics = mShowmapSnapshotHelper.getMetrics();
+ assertFalse(metrics.isEmpty());
+ for (String processName : CACHED_PROCESS_LIST) {
+ assertFalse(
+ metrics.containsKey(
+ constructKey(
+ String.format(
+ ShowmapSnapshotHelper.OUTPUT_METRIC_PATTERN, "rss"),
+ processName)));
+ assertFalse(
+ metrics.containsKey(
+ constructKey(
+ String.format(
+ ShowmapSnapshotHelper.OUTPUT_METRIC_PATTERN, "pss"),
+ processName)));
+ }
+ assertTrue(metrics.containsKey(ShowmapSnapshotHelper.OUTPUT_FILE_PATH_KEY));
+ }
+
+ /** Test isCachedProcess() from cached process only. */
+ @Test
+ public void testGetMetrics_CachedProcess() throws IOException {
+ doReturn("1000")
+ .when(mShowmapSnapshotHelper)
+ .executeShellCommand(matches(mShowmapSnapshotHelper.OOM_SCORE_ADJ_CMD));
+ mShowmapSnapshotHelper.setUp(VALID_OUTPUT_DIR, CACHED_PROCESS_LIST);
+ mShowmapSnapshotHelper.setMetricNameIndex(METRIC_INDEX_STR);
+ assertTrue(mShowmapSnapshotHelper.startCollecting());
+ Map<String, String> metrics = mShowmapSnapshotHelper.getMetrics();
+ assertFalse(metrics.isEmpty());
+ for (String processName : CACHED_PROCESS_LIST) {
+ assertFalse(
+ metrics.containsKey(
+ constructKey(
+ String.format(
+ ShowmapSnapshotHelper.OUTPUT_METRIC_PATTERN, "rss"),
+ processName)));
+ assertFalse(
+ metrics.containsKey(
+ constructKey(
+ String.format(
+ ShowmapSnapshotHelper.OUTPUT_METRIC_PATTERN, "pss"),
+ processName)));
+ }
+ assertTrue(metrics.containsKey(ShowmapSnapshotHelper.OUTPUT_FILE_PATH_KEY));
+ }
+
/**
* Test all process flag return more than 2 processes metrics at least.
*/
@@ -158,7 +243,6 @@
Map<String, String> metrics = mShowmapSnapshotHelper.getMetrics();
assertTrue(metrics.size() > 2);
assertTrue(metrics.containsKey(ShowmapSnapshotHelper.OUTPUT_FILE_PATH_KEY));
-
}
@Test
@@ -211,7 +295,6 @@
mShowmapSnapshotHelper.setAllProcesses();
assertTrue(mShowmapSnapshotHelper.startCollecting());
Map<String, String> metrics = mShowmapSnapshotHelper.getMetrics();
-
assertTrue(metrics.size() != 0);
// process count, process with child process count and path to snapshot file in the output
@@ -226,6 +309,19 @@
// At least one process (i.e init) will have child process
assertTrue(parentWithChildProcessSet.size() > 0);
assertTrue(metrics.containsKey(ShowmapSnapshotHelper.CHILD_PROCESS_COUNT_PREFIX + "_init"));
+ // These assertions are for checking SKIP_PROCESS
+ assertFalse(
+ metrics.containsKey(
+ ShowmapSnapshotHelper.PARENT_PROCESS_STRING
+ + "_init_"
+ + ShowmapSnapshotHelper.CHILD_PROCESS_STRING
+ + "_logcat"));
+ assertFalse(
+ metrics.containsKey(
+ ShowmapSnapshotHelper.PARENT_PROCESS_STRING
+ + "_init_"
+ + ShowmapSnapshotHelper.CHILD_PROCESS_STRING
+ + "_sh"));
}
@Test
@@ -265,6 +361,7 @@
}
private void testProcessList(String metricIndexStr, String... processNames) {
+ doReturn(false).when(mShowmapSnapshotHelper).isCachedProcess(anyString(), anyLong());
mShowmapSnapshotHelper.setUp(VALID_OUTPUT_DIR, processNames);
mShowmapSnapshotHelper.setMetricNameIndex(metricIndexStr);
assertTrue(mShowmapSnapshotHelper.startCollecting());
@@ -280,4 +377,35 @@
}
assertTrue(metrics.containsKey(ShowmapSnapshotHelper.OUTPUT_FILE_PATH_KEY));
}
+
+ /** Test count threads. */
+ @Test
+ public void testCountThreads() throws IOException {
+ String countThreadsSampleOutput =
+ "threads_count_com.google.android.inputmethod.latin : 41\n"
+ + "threads_count_com.google.android.ims : 35\n"
+ + "threads_count_ : 1\n"
+ + "threads_count_com.google.android.connectivitymonitor : 21\n";
+ doReturn(countThreadsSampleOutput)
+ .when(mShowmapSnapshotHelper)
+ .executeShellCommand(matches(mShowmapSnapshotHelper.THREADS_CMD));
+ mShowmapSnapshotHelper.setCountThreadsOption(true);
+ Map<String, String> metrics = mShowmapSnapshotHelper.getMetrics();
+ assertFalse(metrics.isEmpty());
+ assertTrue(metrics.size() == 3);
+ metrics.forEach(
+ (key, value) -> {
+ assertTrue(key.contains("threads_count_"));
+ });
+ }
+
+ /** Test count threads. */
+ @Test
+ public void testNoCountThreads() throws IOException {
+ mShowmapSnapshotHelper.setCountThreadsOption(false);
+ Map<String, String> metrics = mShowmapSnapshotHelper.getMetrics();
+ assertTrue(metrics.isEmpty());
+ verify(mShowmapSnapshotHelper, never())
+ .executeShellCommand(matches(mShowmapSnapshotHelper.THREADS_CMD));
+ }
}