release-request-160c4b31-7fa0-4e2b-aabe-85380836a1ce-for-git_oc-release-4129081 snap-temp-L15300000077039010

Change-Id: Iab70b7a9fa12e08062e41550abb59fbaa2702e80
diff --git a/src/com/android/tradefed/testtype/TfTestLauncher.java b/src/com/android/tradefed/testtype/TfTestLauncher.java
index c5cc4ec..87aae1f 100644
--- a/src/com/android/tradefed/testtype/TfTestLauncher.java
+++ b/src/com/android/tradefed/testtype/TfTestLauncher.java
@@ -26,6 +26,7 @@
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
 import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.HprofAllocSiteParser;
 import com.android.tradefed.util.RunUtil;
 import com.android.tradefed.util.StreamUtil;
 
@@ -209,13 +210,7 @@
             }
         }
         if (mEnableHprof) {
-            InputStreamSource memory = null;
-            try {
-                memory = new FileInputStreamSource(mHprofFile);
-                listener.testLog("hprof", LogDataType.TEXT, memory);
-            } finally {
-                StreamUtil.cancel(memory);
-            }
+            logHprofResults(mHprofFile, listener);
         }
 
         if (mTmpDir != null) {
@@ -359,4 +354,43 @@
         listener.testEnded(tid, Collections.emptyMap());
         listener.testRunEnded(0, Collections.emptyMap());
     }
+
+    /**
+     * Helper to log and report as metric the hprof data.
+     *
+     * @param hprofFile file containing the Hprof report
+     * @param listener the {@link ITestInvocationListener} where to report the test.
+     */
+    private void logHprofResults(File hprofFile, ITestInvocationListener listener) {
+        if (hprofFile == null) {
+            CLog.w("Hprof file was null. Skipping parsing.");
+            return;
+        }
+        if (!hprofFile.exists()) {
+            CLog.w("Hprof file %s was not found. Skipping parsing.", hprofFile.getAbsolutePath());
+            return;
+        }
+        InputStreamSource memory = null;
+        try {
+            memory = new FileInputStreamSource(hprofFile);
+            listener.testLog("hprof", LogDataType.TEXT, memory);
+        } finally {
+            StreamUtil.cancel(memory);
+        }
+        HprofAllocSiteParser parser = new HprofAllocSiteParser();
+        try {
+            Map<String, String> results = parser.parse(hprofFile);
+            if (results.isEmpty()) {
+                CLog.d("No allocation site found from hprof file");
+                return;
+            }
+            listener.testRunStarted("hprofAllocSites", 1);
+            TestIdentifier tid = new TestIdentifier("hprof", "allocationSites");
+            listener.testStarted(tid);
+            listener.testEnded(tid, results);
+            listener.testRunEnded(0, Collections.emptyMap());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/src/com/android/tradefed/util/HprofAllocSiteParser.java b/src/com/android/tradefed/util/HprofAllocSiteParser.java
new file mode 100644
index 0000000..53519b7
--- /dev/null
+++ b/src/com/android/tradefed/util/HprofAllocSiteParser.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 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.tradefed.util;
+
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Helper class to parse info from an Allocation Sites section of hprof reports. */
+public class HprofAllocSiteParser {
+
+    private static final String ALLOC_SITES_START_PATTERN = "SITES BEGIN";
+    private static final String ALLOC_SITES_END_PATTERN = "SITES END";
+    private boolean mHasAllocSiteStarted = false;
+    // format:
+    //            percent          live          alloc'ed  stack class
+    //   rank   self  accum     bytes objs     bytes  objs trace name
+    //      1 12.24% 12.24%  12441616    1  12441616     1 586322 byte[]
+    private static final Pattern RANK_PATTERN =
+            Pattern.compile(
+                    "(\\s+)([0-9]*)(\\s+)([0-9]*\\.?[0-9]+%)(\\s+)([0-9]*\\.?[0-9]+%)(\\s+)"
+                            + "([0-9]+)(\\s+)([0-9]+)(\\s+)([0-9]+)(\\s+)([0-9]+)(\\s+)([0-9]+)"
+                            + "(\\s+)(.*)");
+
+    /**
+     * Parse a text hprof report.
+     *
+     * @param hprofReport file containing the hprof report.
+     * @return a Map containing the results
+     */
+    public Map<String, String> parse(File hprofReport) throws IOException {
+        Map<String, String> results = new HashMap<>();
+        if (hprofReport == null || !hprofReport.exists()) {
+            return results;
+        }
+        internalParse(hprofReport, results);
+        return results;
+    }
+
+    /**
+     * Actual parsing line by line of the report to extract information.
+     *
+     * @param report the {@link File} containing the hprof report.
+     * @param currentRes the {@link Map} where the allocation sites will be stored.
+     */
+    private void internalParse(File report, Map<String, String> currentRes) throws IOException {
+        try (BufferedReader br = new BufferedReader(new FileReader(report))) {
+            for (String line; (line = br.readLine()) != null; ) {
+                handleAllocSites(line, currentRes);
+            }
+        }
+    }
+
+    /** Handles the allocation sites in the hprof report. */
+    private void handleAllocSites(String line, Map<String, String> currentRes) {
+        if (line.startsWith(ALLOC_SITES_START_PATTERN)) {
+            mHasAllocSiteStarted = true;
+        } else if (line.startsWith(ALLOC_SITES_END_PATTERN)) {
+            mHasAllocSiteStarted = false;
+        } else if (mHasAllocSiteStarted) {
+            Matcher m = RANK_PATTERN.matcher(line);
+            if (m.find()) {
+                CLog.d(
+                        "Rank %s-%s-%s-%s-%s-%s-%s-%s-%s",
+                        m.group(2),
+                        m.group(4),
+                        m.group(6),
+                        m.group(8),
+                        m.group(10),
+                        m.group(12),
+                        m.group(14),
+                        m.group(16),
+                        m.group(18));
+                currentRes.put(String.format("Rank%s", m.group(2)), m.group(12));
+            }
+        }
+    }
+}
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index a301985..62ff479 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -174,6 +174,7 @@
 import com.android.tradefed.util.FakeTestsZipFolderTest;
 import com.android.tradefed.util.FileUtilTest;
 import com.android.tradefed.util.FixedByteArrayOutputStreamTest;
+import com.android.tradefed.util.HprofAllocSiteParserTest;
 import com.android.tradefed.util.JUnitXmlParserTest;
 import com.android.tradefed.util.KeyguardControllerStateTest;
 import com.android.tradefed.util.ListInstrumentationParserTest;
@@ -418,6 +419,7 @@
     FakeTestsZipFolderTest.class,
     FileUtilTest.class,
     FixedByteArrayOutputStreamTest.class,
+    HprofAllocSiteParserTest.class,
     HttpHelperTest.class,
     HttpMultipartPostTest.class,
     JUnitXmlParserTest.class,
diff --git a/tests/src/com/android/tradefed/util/HprofAllocSiteParserTest.java b/tests/src/com/android/tradefed/util/HprofAllocSiteParserTest.java
new file mode 100644
index 0000000..829d76a
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/HprofAllocSiteParserTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 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.tradefed.util;
+
+import static org.junit.Assert.*;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.util.Map;
+
+/** Unit tests for {@link HprofAllocSiteParser}. */
+@RunWith(JUnit4.class)
+public class HprofAllocSiteParserTest {
+
+    private static final String TEST_STRING =
+            "java.util.WeakHashMap$KeySet.iterator\n"
+                    + "        java.util.AbstractCollection.toArray(AbstractCollection.java:180)\n"
+                    + "        com.sun.imageio.stream.StreamCloser$1.run(StreamCloser.java:70)\n"
+                    + "        java.lang.Thread.run(Thread.java:745)\n"
+                    + "SITES BEGIN (ordered by live bytes) Mon Jun  5 04:35:20 2017\n"
+                    + "          percent          live          alloc'ed  stack class\n"
+                    + " rank   self  accum     bytes objs     bytes  objs trace name\n"
+                    + "    1 12.24% 12.24%  12441616    1  12441616     1 586322 byte[]\n"
+                    + "    2  6.87% 19.12%   6983280 145485  10509264 218943 977169 HeapChaBuffer\n"
+                    + "    3  6.12% 25.24%   6220816    1   6220816     1 586500 byte[]\n"
+                    + "    4  4.60% 29.84%   4676976 97437   6912816 144017 977186 HeapChaBuffer\n"
+                    + "    5  3.44% 33.28%   3491640 145485   5254632 218943 977168 char[]\n"
+                    + "    6  2.68% 35.96%   2727808 17547   2831208 18167 303028 char[]\n"
+                    + "    7  2.30% 38.26%   2338488 97437   3456408 144017 977185 char[]\n"
+                    + "    8  2.26% 40.52%   2294880    6   2294880     6 1069475 char[]\n"
+                    + "    9  2.06% 42.59%   2097168    1   2097168     1 1063791 byte[]\n"
+                    + "   10  1.82% 44.41%   1853888 33022   1881488 33455 303005 char[]\n"
+                    + "   11  1.21% 45.62%   1227208 15226   2047424 33452 303003 char[]\n"
+                    + "   12  1.11% 46.72%   1123248    1   1123248     1 1063811 byte[]\n"
+                    + "   13  1.04% 47.76%   1056704 33022   1070560 33455 303051 OptionDef\n"
+                    + "   14  1.03% 48.79%   1048592    1   1048592     1 970204 byte[]\n"
+                    + "   15  1.03% 49.83%   1048592    1   2080976    13 975125 byte[]\n"
+                    + "SITES END";
+
+    private HprofAllocSiteParser mParser;
+
+    @Before
+    public void setUp() {
+        mParser = new HprofAllocSiteParser();
+    }
+
+    /** Test that {@link HprofAllocSiteParser#parse(File)} returns correctly a map of results. */
+    @Test
+    public void testParse() throws Exception {
+        File f = FileUtil.createTempFile("hprof", ".test");
+        try {
+            FileUtil.writeToFile(TEST_STRING, f);
+            Map<String, String> results = mParser.parse(f);
+            assertFalse(results.isEmpty());
+            assertEquals(15, results.size());
+            assertEquals("2294880", results.get("Rank8"));
+        } finally {
+            FileUtil.deleteFile(f);
+        }
+    }
+
+    /** Test that when the parsing does not find any valid pattern, we return no results. */
+    @Test
+    public void testParse_invalidContent() throws Exception {
+        File f = FileUtil.createTempFile("hprof", ".test");
+        try {
+            FileUtil.writeToFile("SITES BEGIN\nugh, we are in big trouble", f);
+            Map<String, String> results = mParser.parse(f);
+            assertTrue(results.isEmpty());
+        } finally {
+            FileUtil.deleteFile(f);
+        }
+    }
+
+    /** Assert that if the file passed for parsing is null we return empty results. */
+    @Test
+    public void testParse_noFile() throws Exception {
+        Map<String, String> results = mParser.parse(null);
+        assertNotNull(results);
+        assertTrue(results.isEmpty());
+    }
+
+    /** Assert that if the file passed for parsing does not exists we return empty results. */
+    @Test
+    public void testParse_fileDoesNotExists() throws Exception {
+        Map<String, String> results = mParser.parse(new File("thisdoesnotexistsatall"));
+        assertNotNull(results);
+        assertTrue(results.isEmpty());
+    }
+}