Fix FreeMemListener and TotalPssListener

Fixes issue in FreeMemListener not parsing the cached proc mem.

Report the metrics in bytes for total pss and mem available cache proc.

Report mem available and cache process meory separately from
FreeMemListener.

Bug: b/137954774

Test: FreeMemHelperTest and TotalPssHelperTest
Change-Id: I61fc8e53384e64f383de6f98ec28df270cec3e3b
diff --git a/libraries/collectors-helper/memory/src/com/android/helpers/FreeMemHelper.java b/libraries/collectors-helper/memory/src/com/android/helpers/FreeMemHelper.java
index bfe9016..da0ff2c 100644
--- a/libraries/collectors-helper/memory/src/com/android/helpers/FreeMemHelper.java
+++ b/libraries/collectors-helper/memory/src/com/android/helpers/FreeMemHelper.java
@@ -21,8 +21,14 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -40,17 +46,18 @@
 public class FreeMemHelper implements ICollectorHelper<Long> {
     private static final String TAG = FreeMemHelper.class.getSimpleName();
     private static final String SEPARATOR = "\\s+";
-    private static final String CACHED_PROCESSES =
-            "dumpsys meminfo|awk '/Total PSS by category:"
-                    + "/{found=0} {if(found) print} /: Cached/{found=1}'|tr -d ' '";
+    private static final String DUMPSYS_MEMIFNO = "dumpsys meminfo";
     private static final String PROC_MEMINFO = "cat /proc/meminfo";
     private static final String LINE_SEPARATOR = "\\n";
     private static final String MEM_AVAILABLE_PATTERN = "^MemAvailable.*";
-    private static final Pattern PID_PATTERN = Pattern.compile("^.*pid(?<processid>[0-9]*).*$");
+    private static final Pattern CACHE_PROC_START_PATTERN = Pattern.compile(".*: Cached$");
+    private static final Pattern PID_PATTERN = Pattern.compile("^.*pid(?<processid> [0-9]*).*$");
     private static final String DUMPSYS_PROCESS = "dumpsys meminfo %s";
     private static final String MEM_TOTAL = "^\\s+TOTAL\\s+.*";
     private static final String PROCESS_ID = "processid";
-    public static final String MEM_AVAILABLE_CACHE_PROC_DIRTY = "MemAvailable_CacheProcDirty_kb";
+    public static final String MEM_AVAILABLE_CACHE_PROC_DIRTY = "MemAvailable_CacheProcDirty_bytes";
+    public static final String PROC_MEMINFO_MEM_AVAILABLE= "proc_meminfo_memavailable_bytes";
+    public static final String DUMPSYS_CACHED_PROC_MEMORY= "dumpsys_cached_procs_memory_bytes";
 
     private UiDevice mUiDevice;
 
@@ -87,25 +94,24 @@
             Log.e(TAG, "MemAvailable is null.");
             return null;
         }
-        long cacheProcDirty = Long.parseLong(memAvailable[1]);
+        Map<String, Long> results = new HashMap<>();
+        long memAvailableProc = Long.parseLong(memAvailable[1]);
+        results.put(PROC_MEMINFO_MEM_AVAILABLE, (memAvailableProc * 1024));
 
-        String cachedProcesses;
-        try {
-            cachedProcesses = mUiDevice.executeShellCommand(CACHED_PROCESSES);
-        } catch (IOException ioe) {
-            Log.e(TAG, "Failed to find cached processes.", ioe);
-            return null;
-        }
+        long cacheProcDirty = memAvailableProc;
+        byte[] dumpsysMemInfoBytes = MetricUtility.executeCommandBlocking(DUMPSYS_MEMIFNO,
+                InstrumentationRegistry.getInstrumentation());
+        List<String> cachedProcList = getCachedProcesses(dumpsysMemInfoBytes);
+        Long cachedProcMemory = 0L;
 
-        String[] processes = cachedProcesses.split(LINE_SEPARATOR);
-
-        for (String process : processes) {
+        for (String process : cachedProcList) {
+            Log.i(TAG, "Cached Process" + process);
             Matcher match;
             if (((match = matches(PID_PATTERN, process))) != null) {
                 String processId = match.group(PROCESS_ID);
                 String processDumpSysMemInfo = String.format(DUMPSYS_PROCESS, processId);
                 String processInfoStr;
-
+                Log.i(TAG, "Process Id of the cached process" + processId);
                 try {
                     processInfoStr = mUiDevice.executeShellCommand(processDumpSysMemInfo);
                 } catch (IOException ioe) {
@@ -124,16 +130,20 @@
                     String[] procDetails = processInfo[0].trim().split(SEPARATOR);
                     int privateDirty = Integer.parseInt(procDetails[2].trim());
                     int privateClean = Integer.parseInt(procDetails[3].trim());
+                    cachedProcMemory = cachedProcMemory + privateDirty + privateClean;
                     cacheProcDirty = cacheProcDirty + privateDirty + privateClean;
-                    Log.d(TAG, "Cached process: " + process + " Private Dirty: "
-                            + privateDirty + " Private Clean: " + privateClean);
+                    Log.i(TAG, "Cached process: " + process + " Private Dirty: "
+                            + (privateDirty * 1024) + " Private Clean: " + (privateClean * 1024));
                 }
             }
         }
 
-        HashMap<String, Long> memAvailableCacheProcDirty = new HashMap<>(1);
-        memAvailableCacheProcDirty.put(MEM_AVAILABLE_CACHE_PROC_DIRTY, cacheProcDirty);
-        return memAvailableCacheProcDirty;
+        // Sum of all the cached process memory.
+        results.put(DUMPSYS_CACHED_PROC_MEMORY, (cachedProcMemory * 1024));
+
+        // Mem available cache proc dirty (memavailable + cachedProcMemory)
+        results.put(MEM_AVAILABLE_CACHE_PROC_DIRTY, (cacheProcDirty * 1024));
+        return results;
     }
 
     /**
@@ -146,4 +156,41 @@
         Matcher ret = pattern.matcher(line);
         return ret.matches() ? ret : null;
     }
+
+    /**
+     * Get cached process information from dumpsys meminfo.
+     *
+     * @param dumpsysMemInfoBytes
+     * @return list of cached processes.
+     */
+    List<String> getCachedProcesses(byte[] dumpsysMemInfoBytes) {
+        List<String> cachedProcessList = new ArrayList<String>();
+        InputStream inputStream = new ByteArrayInputStream(dumpsysMemInfoBytes);
+        boolean isCacheProcSection = false;
+        try (BufferedReader bfReader = new BufferedReader(new InputStreamReader(inputStream))) {
+            String currLine = null;
+            while ((currLine = bfReader.readLine()) != null) {
+                Log.i(TAG, currLine);
+                Matcher match;
+                if (!isCacheProcSection
+                        && ((match = matches(CACHE_PROC_START_PATTERN, currLine))) == null) {
+                    // Continue untill the start of cache proc section.
+                    continue;
+                } else {
+                    if (isCacheProcSection) {
+                        // If empty we encountered the end of cached process logging.
+                        if (!currLine.isEmpty()) {
+                            cachedProcessList.add(currLine.trim());
+                        } else {
+                            break;
+                        }
+                    }
+                    isCacheProcSection = true;
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return cachedProcessList;
+    }
 }
diff --git a/libraries/collectors-helper/memory/src/com/android/helpers/TotalPssHelper.java b/libraries/collectors-helper/memory/src/com/android/helpers/TotalPssHelper.java
index 4e20c74..7318bc1 100644
--- a/libraries/collectors-helper/memory/src/com/android/helpers/TotalPssHelper.java
+++ b/libraries/collectors-helper/memory/src/com/android/helpers/TotalPssHelper.java
@@ -43,7 +43,7 @@
     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 static final String PSS_METRIC_PREFIX = "am_totalpss_bytes";
 
     private String[] mProcessNames;
     // Minimum number of iterations needed before deciding on the memory usage.
@@ -110,7 +110,8 @@
             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);
+                // Final metric reported in bytes.
+                mPssFinalMap.put(constructKey(PSS_METRIC_PREFIX, processName), pss * 1024);
                 return;
             }
             iteration++;
@@ -118,7 +119,8 @@
 
         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));
+        // Final metric reported in bytes.
+        mPssFinalMap.put(constructKey(PSS_METRIC_PREFIX, processName), average(pssData) * 1024);
     }
 
     /**
diff --git a/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/FreeMemHelperTest.java b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/FreeMemHelperTest.java
index a6bac08..283ab36 100644
--- a/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/FreeMemHelperTest.java
+++ b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/FreeMemHelperTest.java
@@ -50,6 +50,10 @@
         Map<String, Long> freeMemMetrics = mFreeMemHelper.getMetrics();
         assertFalse(freeMemMetrics.isEmpty());
         assertTrue(freeMemMetrics.containsKey(FreeMemHelper.MEM_AVAILABLE_CACHE_PROC_DIRTY));
+        assertTrue(freeMemMetrics.containsKey(FreeMemHelper.PROC_MEMINFO_MEM_AVAILABLE));
+        assertTrue(freeMemMetrics.containsKey(FreeMemHelper.DUMPSYS_CACHED_PROC_MEMORY));
         assertTrue(freeMemMetrics.get(FreeMemHelper.MEM_AVAILABLE_CACHE_PROC_DIRTY) > 0);
+        assertTrue(freeMemMetrics.get(FreeMemHelper.PROC_MEMINFO_MEM_AVAILABLE) > 0);
+        assertTrue(freeMemMetrics.get(FreeMemHelper.DUMPSYS_CACHED_PROC_MEMORY) > 0);
     }
 }
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
index b3dd138..1449040 100644
--- 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
@@ -47,7 +47,7 @@
     // 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 static final String PSS_METRIC_PREFIX = "am_totalpss_bytes";
 
     private TotalPssHelper mTotalPssHelper;
 
diff --git a/libraries/collectors-helper/utilities/src/com/android/helpers/MetricUtility.java b/libraries/collectors-helper/utilities/src/com/android/helpers/MetricUtility.java
index 9cca12b..23462cb 100644
--- a/libraries/collectors-helper/utilities/src/com/android/helpers/MetricUtility.java
+++ b/libraries/collectors-helper/utilities/src/com/android/helpers/MetricUtility.java
@@ -1,5 +1,12 @@
 package com.android.helpers;
 
+import android.app.Instrumentation;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.Map;
 
 /**
@@ -8,9 +15,12 @@
  */
 public class MetricUtility {
 
+    private static final String TAG = MetricUtility.class.getSimpleName();
     private static final String KEY_JOIN = "_";
     private static final String METRIC_SEPARATOR = ",";
 
+    public static final int BUFFER_SIZE = 1024;
+
     /**
      * Append the given array of string to construct the final key used to track the metrics.
      *
@@ -44,4 +54,29 @@
         resultMap.compute(metricKey, (key, value) -> (value == null) ? 1 : value + 1);
     }
 
+    /**
+     * Turn executeShellCommand into a blocking operation.
+     *
+     * @param command shell command to be executed.
+     * @param instr used to run the shell command.
+     * @return byte array of execution result
+     */
+    public static byte[] executeCommandBlocking(String command, Instrumentation instr) {
+        try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(instr.getUiAutomation()
+                .executeShellCommand(command));
+                ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+            byte[] buf = new byte[BUFFER_SIZE];
+            int length;
+            Log.i(TAG, "Start reading the data");
+            while ((length = is.read(buf)) >= 0) {
+                out.write(buf, 0, length);
+            }
+            Log.i(TAG, "Stop reading the data");
+            return out.toByteArray();
+        } catch (IOException e) {
+            Log.e(TAG, "Error executing: " + command, e);
+            return null;
+        }
+    }
+
 }
diff --git a/libraries/device-collectors/src/test/java/android/device/collectors/FreeMemListenerTest.java b/libraries/device-collectors/src/test/java/android/device/collectors/FreeMemListenerTest.java
deleted file mode 100644
index c399125..0000000
--- a/libraries/device-collectors/src/test/java/android/device/collectors/FreeMemListenerTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.app.Instrumentation;
-import android.os.Bundle;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.helpers.FreeMemHelper;
-
-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;
-
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-/**
- * Android Unit tests for {@link FreeMemListener}.
- *
- * To run:
- * atest CollectorDeviceLibTest:android.device.collectors.FreeMemListenerTest
- */
-@RunWith(AndroidJUnit4.class)
-public class FreeMemListenerTest {
-
-    @Mock
-    private Instrumentation mInstrumentation;
-    @Mock
-    private FreeMemHelper mFreeMemHelper;
-
-    private FreeMemListener mListener;
-    private Description mRunDesc;
-    private Description mTest1Desc;
-    private DataRecord mDataRecord;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mRunDesc = Description.createSuiteDescription("run");
-        mTest1Desc = Description.createTestDescription("run", "test1");
-    }
-
-    private FreeMemListener initListener() {
-        FreeMemListener listener = new FreeMemListener(new Bundle(), mFreeMemHelper);
-        listener.setInstrumentation(mInstrumentation);
-        mDataRecord = listener.createDataRecord();
-        return listener;
-    }
-
-    @Test
-    public void testFreeMemHelperCalls() throws Exception {
-        mListener = initListener();
-        mListener.testRunStarted(mRunDesc);
-
-        // Test test start behavior
-        mListener.testStarted(mTest1Desc);
-        verify(mFreeMemHelper, times(1)).startCollecting();
-        mListener.onTestEnd(mDataRecord, mTest1Desc);
-        verify(mFreeMemHelper, times(1)).stopCollecting();
-    }
-}