[vm, service] Include mapped files in GetProcessMemoryUsage.

TEST=ci
Bug: https://github.com/dart-lang/sdk/issues/46166
Change-Id: I66322c70e066bb89c1d568848e17f2ec4f8552d3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/201444
Commit-Queue: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index 00032a4..729c187 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -4234,6 +4234,70 @@
   PrintSuccess(js);
 }
 
+#if defined(HOST_OS_LINUX) || defined(HOST_OS_ANDROID)
+struct VMMapping {
+  char path[256];
+  size_t size;
+};
+
+static void AddVMMappings(JSONArray* rss_children) {
+  FILE* fp = fopen("/proc/self/smaps", "r");
+  if (fp == nullptr) {
+    return;
+  }
+
+  MallocGrowableArray<VMMapping> mappings(10);
+  char line[256];
+  char path[256];
+  char property[32];
+  size_t start, end, size;
+  while (fgets(line, sizeof(line), fp) != nullptr) {
+    if (sscanf(line, "%zx-%zx", &start, &end) == 2) {
+      // Mapping line.
+      strncpy(path, strrchr(line, ' ') + 1, sizeof(path));
+      int len = strlen(path);
+      if ((len > 0) && path[len - 1] == '\n') {
+        path[len - 1] = 0;
+      }
+    } else if (sscanf(line, "%s%zd", property, &size) == 2) {
+      // Property line.
+      // Skipping a few paths to avoid double counting:
+      // (deleted) - memfd dual mapping in Dart heap
+      // [heap] - sbrk area, should already included with malloc
+      // <empty> - anonymous mappings, mostly in Dart heap
+      if ((strcmp(property, "Rss:") == 0) && (size != 0) &&
+          (strcmp(path, "(deleted)") != 0) && (strcmp(path, "[heap]") != 0) &&
+          (strcmp(path, "") != 0)) {
+        bool updated = false;
+        for (intptr_t i = 0; i < mappings.length(); i++) {
+          if (strcmp(mappings[i].path, path) == 0) {
+            mappings[i].size += size;
+            updated = true;
+            break;
+          }
+        }
+        if (!updated) {
+          VMMapping mapping;
+          strncpy(mapping.path, path, sizeof(mapping.path));
+          mapping.size = size;
+          mappings.Add(mapping);
+        }
+      }
+    }
+  }
+  fclose(fp);
+
+  for (intptr_t i = 0; i < mappings.length(); i++) {
+    JSONObject mapping(rss_children);
+    mapping.AddProperty("name", mappings[i].path);
+    mapping.AddProperty("description",
+                        "Mapped file / shared library / executable");
+    mapping.AddProperty64("size", mappings[i].size * KB);
+    JSONArray(&mapping, "children");
+  }
+}
+#endif
+
 static intptr_t GetProcessMemoryUsageHelper(JSONStream* js) {
   JSONObject response(js);
   response.AddProperty("type", "ProcessMemoryUsage");
@@ -4335,6 +4399,8 @@
     vm.AddProperty64("size", vm_size);
   }
 
+  // On Android, malloc is better labeled by /proc/self/smaps.
+#if !defined(HOST_OS_ANDROID)
   intptr_t used, capacity;
   const char* implementation;
   if (MallocHooks::GetStats(&used, &capacity, &implementation)) {
@@ -4360,6 +4426,12 @@
       JSONArray(&malloc_free, "children");
     }
   }
+#endif
+
+#if defined(HOST_OS_LINUX) || defined(HOST_OS_ANDROID)
+  AddVMMappings(&rss_children);
+#endif
+  // TODO(46166): Implement for other operating systems.
 
   return vm_size;
 }