Replace vector usage in collect_vma with map

This change updates the collect_vma callback in showmap.cpp to use a map
to accumulate VMAs instead of a vector. This is done because the
insertion function used for the -a (address) and -v (verbose) options is
expensive, and is done in O(# of VMAs) time. This improves the runtime
complexity to O(log(# of VMAs)). collect_vma_merge_by_names has been
combined with collect_vma.

This drastically improves showmap's CPU usage. Over 3 trials each:
1) showmap -a: 37% reduction in CPU cycles spent in showmap
2) showmap -v: 67% reduction "
3) showmap -a -v: 35% reduction "

Test: Check that showmap still functions as expected (with various
      options); measure CPU improvements.
Bug: 229147699
Change-Id: I6d9d40189883a98f4f434ac940967561d9cf26fb
diff --git a/tools/showmap.cpp b/tools/showmap.cpp
index 0377c22..51bbda1 100644
--- a/tools/showmap.cpp
+++ b/tools/showmap.cpp
@@ -34,15 +34,16 @@
 #include <android-base/strings.h>
 #include <meminfo/procmeminfo.h>
 
+using ::android::base::StringPrintf;
 using ::android::meminfo::EscapeCsvString;
 using ::android::meminfo::EscapeJsonString;
 using ::android::meminfo::Format;
 using ::android::meminfo::GetFormat;
+using ::android::meminfo::MemUsage;
 using ::android::meminfo::Vma;
 
 // Global options
 static std::string g_filename;
-static bool g_merge_by_names = false;
 static bool g_terse = false;
 static bool g_verbose = false;
 static bool g_show_addr = false;
@@ -199,9 +200,7 @@
     output << ",\"object\":" << EscapeJsonString(get_vma_name(vma, total, is_bss)) << "}";
 }
 
-static VmaInfo g_total;
-static std::vector<VmaInfo> g_vmas;
-static std::map<std::string, VmaInfo> g_vmas_name_map;
+static std::multimap<std::string, VmaInfo> g_vmas;
 
 [[noreturn]] static void usage(const char* progname, int exit_status) {
     std::cerr << progname << " [-aqtv] [-f FILE] PID\n"
@@ -220,14 +219,6 @@
     return (name.size() > 4) && (name[0] == '/') && ::android::base::EndsWith(name, ".so");
 }
 
-static bool insert_before(const VmaInfo& a, const VmaInfo& b) {
-    if (g_show_addr) {
-        return (a.vma.start < b.vma.start || (a.vma.start == b.vma.start && a.vma.end < b.vma.end));
-    }
-
-    return strcmp(a.vma.name.c_str(), b.vma.name.c_str()) < 0;
-}
-
 static void infer_vma_name(VmaInfo& current, const VmaInfo& recent) {
     if (current.vma.name.empty()) {
         if (recent.vma.end == current.vma.start && is_library(recent.vma.name)) {
@@ -239,11 +230,40 @@
     }
 }
 
+static void add_mem_usage(MemUsage* to, const MemUsage& from) {
+    to->vss += from.vss;
+    to->rss += from.rss;
+    to->pss += from.pss;
+
+    to->swap += from.swap;
+    to->swap_pss += from.swap_pss;
+
+    to->private_clean += from.private_clean;
+    to->private_dirty += from.private_dirty;
+    to->shared_clean += from.shared_clean;
+    to->shared_dirty += from.shared_dirty;
+
+    to->anon_huge_pages += from.anon_huge_pages;
+    to->shmem_pmd_mapped += from.shmem_pmd_mapped;
+    to->file_pmd_mapped += from.file_pmd_mapped;
+    to->shared_hugetlb += from.shared_hugetlb;
+    to->private_hugetlb += from.private_hugetlb;
+}
+
 static void collect_vma(const Vma& vma) {
     static VmaInfo recent;
     VmaInfo current(vma);
+
+    std::string key;
+    if (g_show_addr) {
+        // vma.end is included in case vma.start is identical for two VMAs.
+        key = StringPrintf("%16" PRIx64 "%16" PRIx64, vma.start, vma.end);
+    } else {
+        key = vma.name;
+    }
+
     if (g_vmas.empty()) {
-        g_vmas.emplace_back(current);
+        g_vmas.emplace(key, current);
         recent = current;
         return;
     }
@@ -251,54 +271,29 @@
     infer_vma_name(current, recent);
     recent = current;
 
-    std::vector<VmaInfo>::iterator it;
-    for (it = g_vmas.begin(); it != g_vmas.end(); it++) {
-        if (insert_before(current, *it)) {
-            g_vmas.insert(it, current);
-            break;
-        }
-    }
-
-    if (it == g_vmas.end()) {
-        g_vmas.emplace_back(current);
-    }
-}
-
-static void collect_vma_merge_by_names(const Vma& vma) {
-    static VmaInfo recent;
-    VmaInfo current(vma);
-    if (g_vmas_name_map.empty()) {
-        g_vmas_name_map.emplace(vma.name, vma);
-        recent = current;
+    // If sorting by address, the VMA can be placed into the map as-is.
+    if (g_show_addr) {
+        g_vmas.emplace(key, current);
         return;
     }
 
-    infer_vma_name(current, recent);
-    recent = current;
-
-    auto iter = g_vmas_name_map.find(current.vma.name);
-    if (iter == g_vmas_name_map.end()) {
-        g_vmas_name_map.emplace(current.vma.name, current);
+    // infer_vma_name() may have changed current.vma.name, so this key needs to be set again before
+    // using it to sort by name. For verbose output, the VMA can immediately be placed into the map.
+    key = current.vma.name;
+    if (g_verbose) {
+        g_vmas.emplace(key, current);
         return;
     }
+
+    // Coalesces VMAs' usage by name, if !g_show_addr && !g_verbose.
+    auto iter = g_vmas.find(key);
+    if (iter == g_vmas.end()) {
+        g_vmas.emplace(key, current);
+        return;
+    }
+
     VmaInfo& match = iter->second;
-    match.vma.usage.vss += current.vma.usage.vss;
-    match.vma.usage.rss += current.vma.usage.rss;
-    match.vma.usage.pss += current.vma.usage.pss;
-
-    match.vma.usage.shared_clean += current.vma.usage.shared_clean;
-    match.vma.usage.shared_dirty += current.vma.usage.shared_dirty;
-    match.vma.usage.private_clean += current.vma.usage.private_clean;
-    match.vma.usage.private_dirty += current.vma.usage.private_dirty;
-    match.vma.usage.swap += current.vma.usage.swap;
-    match.vma.usage.swap_pss += current.vma.usage.swap_pss;
-
-    match.vma.usage.anon_huge_pages += current.vma.usage.anon_huge_pages;
-    match.vma.usage.shmem_pmd_mapped += current.vma.usage.shmem_pmd_mapped;
-    match.vma.usage.file_pmd_mapped += current.vma.usage.file_pmd_mapped;
-    match.vma.usage.shared_hugetlb += current.vma.usage.shared_hugetlb;
-    match.vma.usage.private_hugetlb += current.vma.usage.shared_hugetlb;
-
+    add_mem_usage(&match.vma.usage, current.vma.usage);
     match.is_bss &= current.is_bss;
 }
 
@@ -339,19 +334,7 @@
 }
 
 static int showmap(Format format) {
-    bool success;
-    if (!g_merge_by_names) {
-        success = ::android::meminfo::ForEachVmaFromFile(g_filename, collect_vma);
-    } else {
-        success = ::android::meminfo::ForEachVmaFromFile(g_filename, collect_vma_merge_by_names);
-        g_vmas.reserve(g_vmas_name_map.size());
-        // VMAs will be returned in lexicographical order of names.
-        for (const auto& entry : g_vmas_name_map) {
-            g_vmas.emplace_back(entry.second);
-        }
-    }
-
-    if (!success) {
+    if (!::android::meminfo::ForEachVmaFromFile(g_filename, collect_vma)) {
         if (!g_quiet) {
             std::cerr << "Failed to parse file " << g_filename << "\n";
         }
@@ -389,20 +372,10 @@
             break;
     }
 
-    for (const auto& v : g_vmas) {
-        g_total.vma.usage.vss += v.vma.usage.vss;
-        g_total.vma.usage.rss += v.vma.usage.rss;
-        g_total.vma.usage.pss += v.vma.usage.pss;
-
-        g_total.vma.usage.private_clean += v.vma.usage.private_clean;
-        g_total.vma.usage.private_dirty += v.vma.usage.private_dirty;
-        g_total.vma.usage.shared_clean += v.vma.usage.shared_clean;
-        g_total.vma.usage.shared_dirty += v.vma.usage.shared_dirty;
-
-        g_total.vma.usage.swap += v.vma.usage.swap;
-        g_total.vma.usage.swap_pss += v.vma.usage.swap_pss;
-        g_total.count += v.count;
-
+    VmaInfo total_usage;
+    for (const auto& entry : g_vmas) {
+        const VmaInfo& v = entry.second;
+        add_mem_usage(&total_usage.vma.usage, v.vma.usage);
         if (g_terse && !(v.vma.usage.private_dirty || v.vma.usage.private_clean)) {
             continue;
         }
@@ -429,13 +402,13 @@
             print_divider(std::cout);
             print_header(std::cout);
             print_divider(std::cout);
-            g_total.to_raw(std::cout, true);
+            total_usage.to_raw(std::cout, true);
             break;
         case Format::CSV:
-            g_total.to_csv(std::cout, true);
+            total_usage.to_csv(std::cout, true);
             break;
         case Format::JSON:
-            g_total.to_json(std::cout, true);
+            total_usage.to_json(std::cout, true);
             std::cout << "]\n";
             break;
         default:
@@ -500,6 +473,5 @@
         g_filename = ::android::base::StringPrintf("/proc/%d/smaps", g_pid);
     }
 
-    g_merge_by_names = !g_verbose && !g_show_addr;
     return showmap(format);
 }