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