libmeminfo: Add ReadSysMemInfo variants

The new variant is primarily used in framework. See: go/ag/5780400
for usage. Also add tests, benchmarks and fix several issues found in SysMemInfo
class.

New benchmark results are:
--------------------------------------------------------------
Benchmark                       Time           CPU Iterations
--------------------------------------------------------------
BM_ReadMemInfo_old           7726 ns       7696 ns      90201
BM_ReadMemInfo_new           7554 ns       7525 ns      90358
BM_ZramTotal_old             6446 ns       6406 ns     108361
BM_ZramTotal_new             6529 ns       6488 ns     106545
BM_MemInfoWithZram_old      14485 ns      14412 ns      48492
BM_MemInfoWithZram_new      20572 ns      20459 ns      33438
--------------------------------------------------------------

The reason for BM_MemInfoWithZram_new shows worse numbers is because
the new API also tries to find more than 1 zram device (if it exists).
The old implementation hard coded everything to "/sys/block/zram0/"

Test: libmeminfo_test 1
Bug: 114325007
Bug: 111694435

Change-Id: I246d9e9a54986ee9b2542d1eaac79ecf7310b23a
Signed-off-by: Sandeep Patil <sspatil@google.com>
diff --git a/libmeminfo/sysmeminfo.cpp b/libmeminfo/sysmeminfo.cpp
index 7e56238..4ec1c99 100644
--- a/libmeminfo/sysmeminfo.cpp
+++ b/libmeminfo/sysmeminfo.cpp
@@ -18,12 +18,15 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 
+#include <algorithm>
 #include <cctype>
 #include <cstdio>
 #include <fstream>
+#include <iterator>
 #include <string>
 #include <utility>
 #include <vector>
@@ -49,7 +52,29 @@
 };
 
 bool SysMemInfo::ReadMemInfo(const std::string& path) {
-    return ReadMemInfo(SysMemInfo::kDefaultSysMemInfoTags, path);
+    return ReadMemInfo(SysMemInfo::kDefaultSysMemInfoTags, path,
+                       [&](const std::string& tag, uint64_t val) { mem_in_kb_[tag] = val; });
+}
+
+bool SysMemInfo::ReadMemInfo(std::vector<uint64_t>* out, const std::string& path) {
+    return ReadMemInfo(SysMemInfo::kDefaultSysMemInfoTags, out, path);
+}
+
+bool SysMemInfo::ReadMemInfo(const std::vector<std::string>& tags, std::vector<uint64_t>* out,
+                             const std::string& path) {
+    out->clear();
+    out->resize(tags.size());
+
+    return ReadMemInfo(tags, path, [&]([[maybe_unused]] const std::string& tag, uint64_t val) {
+        auto it = std::find(tags.begin(), tags.end(), tag);
+        if (it == tags.end()) {
+            LOG(ERROR) << "Tried to store invalid tag: " << tag;
+            return;
+        }
+        auto index = std::distance(tags.begin(), it);
+        // store the values in the same order as the tags
+        out->at(index) = val;
+    });
 }
 
 // TODO: Delete this function if it can't match up with the c-like implementation below.
@@ -88,7 +113,8 @@
 }
 
 #else
-bool SysMemInfo::ReadMemInfo(const std::vector<std::string>& tags, const std::string& path) {
+bool SysMemInfo::ReadMemInfo(const std::vector<std::string>& tags, const std::string& path,
+                             std::function<void(const std::string&, uint64_t)> store_val) {
     char buffer[4096];
     int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
     if (fd < 0) {
@@ -106,22 +132,34 @@
     char* p = buffer;
     uint32_t found = 0;
     uint32_t lineno = 0;
+    bool zram_tag_found = false;
     while (*p && found < tags.size()) {
         for (auto& tag : tags) {
+            // Special case for "Zram:" tag that android_os_Debug and friends look
+            // up along with the rest of the numbers from /proc/meminfo
+            if (!zram_tag_found && tag == "Zram:") {
+                store_val(tag, mem_zram_kb());
+                zram_tag_found = true;
+                found++;
+                continue;
+            }
+
             if (strncmp(p, tag.c_str(), tag.size()) == 0) {
                 p += tag.size();
                 while (*p == ' ') p++;
                 char* endptr = nullptr;
-                mem_in_kb_[tag] = strtoull(p, &endptr, 10);
+                uint64_t val = strtoull(p, &endptr, 10);
                 if (p == endptr) {
                     PLOG(ERROR) << "Failed to parse line:" << lineno + 1 << " in file: " << path;
                     return false;
                 }
+                store_val(tag, val);
                 p = endptr;
                 found++;
                 break;
             }
         }
+
         while (*p && *p != '\n') {
             p++;
         }
@@ -163,24 +201,19 @@
 }
 
 bool SysMemInfo::MemZramDevice(const std::string& zram_dev, uint64_t* mem_zram_dev) {
-    std::string content;
-    if (android::base::ReadFileToString(zram_dev + "mm_stat", &content)) {
-        std::vector<std::string> values = ::android::base::Split(content, " ");
-        if (values.size() < 3) {
-            LOG(ERROR) << "Malformed mm_stat file for zram dev: " << zram_dev
-                       << " content: " << content;
+    std::string mmstat = ::android::base::StringPrintf("%s/%s", zram_dev.c_str(), "mm_stat");
+    auto mmstat_fp = std::unique_ptr<FILE, decltype(&fclose)>{fopen(mmstat.c_str(), "re"), fclose};
+    if (mmstat_fp != nullptr) {
+        // only if we do have mmstat, use it. Otherwise, fall through to trying out the old
+        // 'mem_used_total'
+        if (fscanf(mmstat_fp.get(), "%*" SCNu64 " %*" SCNu64 " %" SCNu64, mem_zram_dev) != 1) {
+            PLOG(ERROR) << "Malformed mm_stat file in: " << zram_dev;
             return false;
         }
-
-        if (!::android::base::ParseUint(values[2], mem_zram_dev)) {
-            LOG(ERROR) << "Malformed mm_stat file for zram dev: " << zram_dev
-                       << " value: " << values[2];
-            return false;
-        }
-
         return true;
     }
 
+    std::string content;
     if (::android::base::ReadFileToString(zram_dev + "mem_used_total", &content)) {
         *mem_zram_dev = strtoull(content.c_str(), NULL, 10);
         if (*mem_zram_dev == ULLONG_MAX) {