Add /proc/<pid>/smaps parser.

Bug: 150930222
Bug: 117646511
Change-Id: I4e2e6916dee4f3e0df027ce1a3ac51c5c6da6fb1
diff --git a/Android.bp b/Android.bp
index 9e60331..cd8f296 100644
--- a/Android.bp
+++ b/Android.bp
@@ -5921,6 +5921,7 @@
     "src/profiling/memory/client_unittest.cc",
     "src/profiling/memory/heapprofd_producer_unittest.cc",
     "src/profiling/memory/page_idle_checker_unittest.cc",
+    "src/profiling/memory/parse_smaps_unittest.cc",
     "src/profiling/memory/sampler_unittest.cc",
     "src/profiling/memory/system_property_unittest.cc",
     "src/profiling/memory/unwinding_unittest.cc",
diff --git a/src/profiling/memory/BUILD.gn b/src/profiling/memory/BUILD.gn
index c1578aa..18f81b3 100644
--- a/src/profiling/memory/BUILD.gn
+++ b/src/profiling/memory/BUILD.gn
@@ -197,6 +197,7 @@
     "client_unittest.cc",
     "heapprofd_producer_unittest.cc",
     "page_idle_checker_unittest.cc",
+    "parse_smaps_unittest.cc",
     "sampler_unittest.cc",
     "system_property_unittest.cc",
     "unwinding_unittest.cc",
diff --git a/src/profiling/memory/parse_smaps.h b/src/profiling/memory/parse_smaps.h
new file mode 100644
index 0000000..452adc2
--- /dev/null
+++ b/src/profiling/memory/parse_smaps.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+// This is a header-only file because we want to use this in the ART plugin,
+// which should not depend on any Perfetto compilation units other than the
+// client API.
+
+#ifndef SRC_PROFILING_MEMORY_PARSE_SMAPS_H_
+#define SRC_PROFILING_MEMORY_PARSE_SMAPS_H_
+
+#include <string>
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <inttypes.h>
+
+namespace perfetto {
+namespace profiling {
+
+struct SmapsEntry {
+  int64_t size_kb = -1;
+  int64_t private_dirty_kb = -1;
+  int64_t swap_kb = -1;
+  std::string pathname;
+};
+
+struct SmapsParserState {
+  bool parsed_header = false;
+  SmapsEntry current_entry{};
+};
+
+template <typename T>
+static bool ParseSmaps(FILE* f, T callback) {
+  SmapsParserState state;
+
+  size_t line_size = 1024;
+  char* line = static_cast<char*>(malloc(line_size));
+
+  for (;;) {
+    errno = 0;
+    ssize_t rd = getline(&line, &line_size, f);
+    if (rd == -1) {
+      free(line);
+      if (state.parsed_header)
+        callback(state.current_entry);
+      return errno == 0;
+    } else {
+      if (line[rd - 1] == '\n') {
+        line[rd - 1] = '\0';
+        rd--;
+      }
+      if (!ParseSmapsLine(line, static_cast<size_t>(rd), &state, callback)) {
+        free(line);
+        return false;
+      }
+    }
+  }
+}
+
+static const char* FindNthToken(const char* line, size_t n, size_t size) {
+  size_t tokens = 0;
+  bool parsing_token = false;
+  for (size_t i = 0; i < size; ++i) {
+    if (!parsing_token && line[i] != ' ') {
+      parsing_token = true;
+      if (tokens++ == n)
+        return line + static_cast<ssize_t>(i);
+    }
+    if (line[i] == ' ')
+      parsing_token = false;
+  }
+  return nullptr;
+}
+
+template <typename T>
+static bool ParseSmapsLine(char* line,
+                           size_t size,
+                           SmapsParserState* state,
+                           T callback) {
+  char* first_token_end = static_cast<char*>(memchr(line, ' ', size));
+  if (first_token_end == nullptr || first_token_end == line)
+    return false;  // Malformed.
+  bool is_header = *(first_token_end - 1) != ':';
+
+  if (is_header) {
+    if (state->parsed_header)
+      callback(state->current_entry);
+
+    state->current_entry = {};
+    const char* last_token_begin = FindNthToken(line, 5u, size);
+    if (last_token_begin)
+      state->current_entry.pathname.assign(last_token_begin);
+    state->parsed_header = true;
+    return true;
+  }
+  if (!state->parsed_header)
+    return false;
+
+  sscanf(line, "Size: %" PRId64 " kB", &state->current_entry.size_kb);
+  sscanf(line, "Swap: %" PRId64 " kB", &state->current_entry.swap_kb);
+  sscanf(line, "Private_Dirty: %" PRId64 " kB",
+         &state->current_entry.private_dirty_kb);
+  return true;
+}
+
+}  // namespace profiling
+}  // namespace perfetto
+
+#endif  // SRC_PROFILING_MEMORY_PARSE_SMAPS_H_
diff --git a/src/profiling/memory/parse_smaps_unittest.cc b/src/profiling/memory/parse_smaps_unittest.cc
new file mode 100644
index 0000000..9e84486
--- /dev/null
+++ b/src/profiling/memory/parse_smaps_unittest.cc
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include "src/profiling/memory/parse_smaps.h"
+
+#include "perfetto/ext/base/scoped_file.h"
+#include "src/base/test/utils.h"
+#include "test/gtest_and_gmock.h"
+
+#include <inttypes.h>
+
+namespace perfetto {
+namespace profiling {
+
+bool operator==(const SmapsEntry& a, const SmapsEntry& b);
+bool operator==(const SmapsEntry& a, const SmapsEntry& b) {
+  return a.pathname == b.pathname && a.size_kb == b.size_kb &&
+         a.private_dirty_kb == b.private_dirty_kb && a.swap_kb == b.swap_kb;
+}
+
+namespace {
+
+using ::testing::ElementsAre;
+
+TEST(ParseSmapsTest, Smoke) {
+  base::ScopedFstream fd(fopen(
+      base::GetTestDataPath("src/profiling/memory/test/data/cat_smaps").c_str(),
+      "r"));
+  std::vector<SmapsEntry> entries;
+  EXPECT_TRUE(ParseSmaps(
+      *fd, [&entries](const SmapsEntry& e) { entries.emplace_back(e); }));
+
+  SmapsEntry cat1;
+  cat1.pathname = "/bin/cat";
+  cat1.size_kb = 8;
+  cat1.private_dirty_kb = 0;
+  cat1.swap_kb = 0;
+  SmapsEntry cat2;
+  cat2.pathname = "/bin/cat";
+  cat2.size_kb = 8;
+  cat2.private_dirty_kb = 0;
+  cat2.swap_kb = 0;
+  SmapsEntry heap;
+  heap.pathname = "[heap stuff]";
+  heap.size_kb = 132;
+  heap.private_dirty_kb = 8;
+  heap.swap_kb = 4;
+  EXPECT_THAT(entries, ElementsAre(cat1, cat2, heap));
+}
+
+TEST(ParseSmapsTest, SmokeNoEol) {
+  base::ScopedFstream fd(fopen(
+      base::GetTestDataPath("src/profiling/memory/test/data/cat_smaps_noeol")
+          .c_str(),
+      "r"));
+  std::vector<SmapsEntry> entries;
+  EXPECT_TRUE(ParseSmaps(
+      *fd, [&entries](const SmapsEntry& e) { entries.emplace_back(e); }));
+
+  SmapsEntry cat1;
+  cat1.pathname = "/bin/cat";
+  cat1.size_kb = 8;
+  cat1.private_dirty_kb = 0;
+  cat1.swap_kb = 0;
+  SmapsEntry cat2;
+  cat2.pathname = "/bin/cat";
+  cat2.size_kb = 8;
+  cat2.private_dirty_kb = 0;
+  cat2.swap_kb = 0;
+  SmapsEntry heap;
+  heap.pathname = "[heap stuff]";
+  heap.size_kb = 132;
+  heap.private_dirty_kb = 8;
+  heap.swap_kb = 4;
+  EXPECT_THAT(entries, ElementsAre(cat1, cat2, heap));
+}
+
+}  // namespace
+}  // namespace profiling
+}  // namespace perfetto
diff --git a/src/profiling/memory/test/data/cat_smaps b/src/profiling/memory/test/data/cat_smaps
new file mode 100644
index 0000000..70b49b1
--- /dev/null
+++ b/src/profiling/memory/test/data/cat_smaps
@@ -0,0 +1,66 @@
+5614e178c000-5614e178e000 r--p 00000000 fe:01 393326                     /bin/cat
+Size:                  8 kB
+KernelPageSize:        4 kB
+MMUPageSize:           4 kB
+Rss:                   8 kB
+Pss:                   8 kB
+Shared_Clean:          0 kB
+Shared_Dirty:          0 kB
+Private_Clean:         8 kB
+Private_Dirty:         0 kB
+Referenced:            8 kB
+Anonymous:             0 kB
+LazyFree:              0 kB
+AnonHugePages:         0 kB
+ShmemPmdMapped:        0 kB
+Shared_Hugetlb:        0 kB
+Private_Hugetlb:       0 kB
+Swap:                  0 kB
+SwapPss:               0 kB
+Locked:                0 kB
+THPeligible:    0
+VmFlags: rd mr mw me dw sd 
+5614e1793000-5614e1795000 r--p 00007000 fe:01 393326                     /bin/cat
+Size:                  8 kB
+KernelPageSize:        4 kB
+MMUPageSize:           4 kB
+Rss:                   8 kB
+Pss:                   8 kB
+Shared_Clean:          0 kB
+Shared_Dirty:          0 kB
+Private_Clean:         8 kB
+Private_Dirty:         0 kB
+Referenced:            8 kB
+Anonymous:             0 kB
+LazyFree:              0 kB
+AnonHugePages:         0 kB
+ShmemPmdMapped:        0 kB
+Shared_Hugetlb:        0 kB
+Private_Hugetlb:       0 kB
+Swap:                  0 kB
+SwapPss:               0 kB
+Locked:                0 kB
+THPeligible:    0
+VmFlags: rd mr mw me dw sd 
+5614e184c000-5614e186d000 rw-p 00000000 00:00 0                          [heap stuff]
+Size:                132 kB
+KernelPageSize:        4 kB
+MMUPageSize:           4 kB
+Rss:                   8 kB
+Pss:                   8 kB
+Shared_Clean:          0 kB
+Shared_Dirty:          0 kB
+Private_Clean:         0 kB
+Private_Dirty:         8 kB
+Referenced:            8 kB
+Anonymous:             8 kB
+LazyFree:              0 kB
+AnonHugePages:         0 kB
+ShmemPmdMapped:        0 kB
+Shared_Hugetlb:        0 kB
+Private_Hugetlb:       0 kB
+Swap:                  4 kB
+SwapPss:               4 kB
+Locked:                0 kB
+THPeligible:    1
+VmFlags: rd wr mr mw me ac sd 
diff --git a/src/profiling/memory/test/data/cat_smaps_noeol b/src/profiling/memory/test/data/cat_smaps_noeol
new file mode 100644
index 0000000..40cbfcb
--- /dev/null
+++ b/src/profiling/memory/test/data/cat_smaps_noeol
@@ -0,0 +1,66 @@
+5614e178c000-5614e178e000 r--p 00000000 fe:01 393326                     /bin/cat
+Size:                  8 kB
+KernelPageSize:        4 kB
+MMUPageSize:           4 kB
+Rss:                   8 kB
+Pss:                   8 kB
+Shared_Clean:          0 kB
+Shared_Dirty:          0 kB
+Private_Clean:         8 kB
+Private_Dirty:         0 kB
+Referenced:            8 kB
+Anonymous:             0 kB
+LazyFree:              0 kB
+AnonHugePages:         0 kB
+ShmemPmdMapped:        0 kB
+Shared_Hugetlb:        0 kB
+Private_Hugetlb:       0 kB
+Swap:                  0 kB
+SwapPss:               0 kB
+Locked:                0 kB
+THPeligible:    0
+VmFlags: rd mr mw me dw sd 
+5614e1793000-5614e1795000 r--p 00007000 fe:01 393326                     /bin/cat
+Size:                  8 kB
+KernelPageSize:        4 kB
+MMUPageSize:           4 kB
+Rss:                   8 kB
+Pss:                   8 kB
+Shared_Clean:          0 kB
+Shared_Dirty:          0 kB
+Private_Clean:         8 kB
+Private_Dirty:         0 kB
+Referenced:            8 kB
+Anonymous:             0 kB
+LazyFree:              0 kB
+AnonHugePages:         0 kB
+ShmemPmdMapped:        0 kB
+Shared_Hugetlb:        0 kB
+Private_Hugetlb:       0 kB
+Swap:                  0 kB
+SwapPss:               0 kB
+Locked:                0 kB
+THPeligible:    0
+VmFlags: rd mr mw me dw sd 
+5614e184c000-5614e186d000 rw-p 00000000 00:00 0                          [heap stuff]
+Size:                132 kB
+KernelPageSize:        4 kB
+MMUPageSize:           4 kB
+Rss:                   8 kB
+Pss:                   8 kB
+Shared_Clean:          0 kB
+Shared_Dirty:          0 kB
+Private_Clean:         0 kB
+Private_Dirty:         8 kB
+Referenced:            8 kB
+Anonymous:             8 kB
+LazyFree:              0 kB
+AnonHugePages:         0 kB
+ShmemPmdMapped:        0 kB
+Shared_Hugetlb:        0 kB
+Private_Hugetlb:       0 kB
+Swap:                  4 kB
+SwapPss:               4 kB
+Locked:                0 kB
+THPeligible:    1
+VmFlags: rd wr mr mw me ac sd 
\ No newline at end of file