Track child process count.

Also track total number of processes with child process.

Bug: b/176130590

Test: atest ShowmapSnapshotHelperTest
Change-Id: Icf48d9407877eec468a2669d4ac9b14c54969fbf
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 2dece36..e3bd233 100644
--- a/libraries/collectors-helper/memory/src/com/android/helpers/ShowmapSnapshotHelper.java
+++ b/libraries/collectors-helper/memory/src/com/android/helpers/ShowmapSnapshotHelper.java
@@ -32,6 +32,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
+import java.util.stream.Collectors;
 
 /**
  * Helper to collect memory information for a list of processes from showmap.
@@ -43,10 +44,15 @@
     private static final String PIDOF_CMD = "pidof %s";
     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";
 
     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";
+    public static final String CHILD_PROCESS_COUNT_PREFIX = "child_processes_count";
+    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 String[] mProcessNames = null;
     private String mTestOutputDir = null;
@@ -144,6 +150,10 @@
                         // 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.
+                        updateChildProcessesCount(processName, pid);
                     }
                 } catch (RuntimeException e) {
                     Log.e(TAG, e.getMessage(), e.getCause());
@@ -151,6 +161,15 @@
                     continue;
                 }
             }
+            // To track total number of process with child processes.
+            if (mMemoryMap.size() != 0) {
+                Set<String> parentWithChildProcessSet = mMemoryMap.keySet()
+                        .stream()
+                        .filter(s -> s.startsWith(CHILD_PROCESS_COUNT_PREFIX))
+                        .collect(Collectors.toSet());
+                mMemoryMap.put(PROCESS_WITH_CHILD_PROCESS_COUNT,
+                        Long.toString(parentWithChildProcessSet.size()));
+            }
             // Store the unique process count. -1 to exclude the "ps" process name.
             mMemoryMap.put(PROCESS_COUNT, Integer.toString(mProcessNames.length - 1));
             writer.close();
@@ -322,6 +341,37 @@
     }
 
     /**
+     * Retrieves the number of child processes for the given process id and updates the total
+     * process count for the process name that pid is associated with.
+     *
+     * @param processName
+     * @param pid
+     */
+    private void updateChildProcessesCount(String processName, long pid) {
+        try {
+            Log.i(TAG,
+                    String.format("Retrieving child processes count for process name: %s with"
+                            + " process id %d.", processName, pid));
+            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);
+
+            if (childProcessCount > 0) {
+                mMemoryMap.put(childCountMetricKey,
+                        Long.toString(
+                                Long.parseLong(mMemoryMap.getOrDefault(childCountMetricKey, "0"))
+                                        + childProcessCount));
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to run child process count command.", e);
+        }
+    }
+
+    /**
      * Enables memory collection for all processes.
      */
     public void setAllProcesses() {
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 4743026..eaca26b 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
@@ -24,6 +24,9 @@
 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;
@@ -142,7 +145,7 @@
     }
 
     /**
-     * Test all process flag return more than 2 processes metrics atleast.
+     * Test all process flag return more than 2 processes metrics at least.
      */
     @Test
     public void testGetMetrics_AllProcess() {
@@ -169,8 +172,9 @@
         mShowmapSnapshotHelper.setAllProcesses();
         assertTrue(mShowmapSnapshotHelper.startCollecting());
         Map<String, String> metrics = mShowmapSnapshotHelper.getMetrics();
-        // process count and path to snapshot file in the output by default.
-        assertTrue(metrics.size() == 2);
+        // process count, process with child process count and path to snapshot file in the output
+        // by default.
+        assertTrue(verifyDefaultMetrics(metrics));
     }
 
     @Test
@@ -181,8 +185,49 @@
         mShowmapSnapshotHelper.setAllProcesses();
         assertTrue(mShowmapSnapshotHelper.startCollecting());
         Map<String, String> metrics = mShowmapSnapshotHelper.getMetrics();
-        // process count and path to snapshot file in the output by default.
-        assertTrue(metrics.size() == 2);
+        // process count, process with child process count and path to snapshot file in the output
+        // by default.
+        assertTrue(verifyDefaultMetrics(metrics));
+    }
+
+    @Test
+    public void testGetMetrics_verify_child_processes_metrics() {
+        mShowmapSnapshotHelper.setUp(VALID_OUTPUT_DIR, NO_PROCESS_LIST);
+        mShowmapSnapshotHelper.setMetricNameIndex(METRIC_EMPTY_INDEX_STR);
+
+        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
+        // by default.
+        assertTrue(metrics.containsKey(ShowmapSnapshotHelper.PROCESS_WITH_CHILD_PROCESS_COUNT));
+
+        Set<String> parentWithChildProcessSet = metrics.keySet()
+                .stream()
+                .filter(s -> s.startsWith(ShowmapSnapshotHelper.CHILD_PROCESS_COUNT_PREFIX))
+                .collect(Collectors.toSet());
+
+        // At least one process (i.e init) will have child process
+        assertTrue(parentWithChildProcessSet.size() > 0);
+        assertTrue(metrics.containsKey(ShowmapSnapshotHelper.CHILD_PROCESS_COUNT_PREFIX + "_init"));
+    }
+
+    private boolean verifyDefaultMetrics(Map<String, String> metrics) {
+        if(metrics.size() == 0) {
+            return false;
+        }
+        for (String key : metrics.keySet()) {
+            if (!(key.equals(ShowmapSnapshotHelper.PROCESS_COUNT)
+                    || key.equals(ShowmapSnapshotHelper.OUTPUT_FILE_PATH_KEY)
+                    || key.equals(ShowmapSnapshotHelper.PROCESS_WITH_CHILD_PROCESS_COUNT)
+                    || key.startsWith(ShowmapSnapshotHelper.CHILD_PROCESS_COUNT_PREFIX))) {
+                return false;
+            }
+        }
+        return true;
     }
 
     private void testProcessList(String metricIndexStr, String... processNames) {