[ VM / Service ] Added `getMemoryUsage` RPC and `MemoryUsage` object to service protocol

Change-Id: If15e49d7aab93382c15529a738b492552faf5376
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/95489
Commit-Queue: Ben Konyi <bkonyi@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
diff --git a/runtime/observatory/tests/service/get_memory_usage.dart b/runtime/observatory/tests/service/get_memory_usage.dart
new file mode 100644
index 0000000..96832c2
--- /dev/null
+++ b/runtime/observatory/tests/service/get_memory_usage.dart
@@ -0,0 +1,49 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:observatory/service_io.dart';
+import 'package:unittest/unittest.dart';
+
+import 'test_helper.dart';
+
+var tests = <VMTest>[
+  (VM vm) async {
+    var params = {
+      'isolateId': vm.isolates.first.id,
+    };
+    var result = await vm.invokeRpcNoUpgrade('getMemoryUsage', params);
+    expect(result['type'], equals('MemoryUsage'));
+    expect(result['heapUsage'], isPositive);
+    expect(result['heapCapacity'], isPositive);
+    expect(result['externalUsage'], isPositive);
+  },
+  (VM vm) async {
+    var params = {
+      'isolateId': 'badid',
+    };
+    bool caughtException;
+    try {
+      await vm.invokeRpcNoUpgrade('getMemoryUsage', params);
+      expect(false, isTrue, reason: 'Unreachable');
+    } on ServerRpcException catch (e) {
+      caughtException = true;
+      expect(e.code, equals(ServerRpcException.kInvalidParams));
+      expect(e.message, "getMemoryUsage: invalid 'isolateId' parameter: badid");
+    }
+    expect(caughtException, isTrue);
+  },
+
+  // Plausible isolate id, not found.
+  (VM vm) async {
+    var params = {
+      'isolateId': 'isolates/9999999999',
+    };
+    var result = await vm.invokeRpcNoUpgrade('getMemoryUsage', params);
+    expect(result['type'], equals('Sentinel'));
+    expect(result['kind'], equals('Collected'));
+    expect(result['valueAsString'], equals('<collected>'));
+  },
+];
+
+main(args) async => runVMTests(args, tests);
diff --git a/runtime/vm/heap/heap.cc b/runtime/vm/heap/heap.cc
index 35675f4..a7220d1 100644
--- a/runtime/vm/heap/heap.cc
+++ b/runtime/vm/heap/heap.cc
@@ -727,6 +727,18 @@
                        : old_space_.ExternalInWords();
 }
 
+int64_t Heap::TotalUsedInWords() const {
+  return UsedInWords(kNew) + UsedInWords(kOld);
+}
+
+int64_t Heap::TotalCapacityInWords() const {
+  return CapacityInWords(kNew) + CapacityInWords(kOld);
+}
+
+int64_t Heap::TotalExternalInWords() const {
+  return ExternalInWords(kNew) + ExternalInWords(kOld);
+}
+
 int64_t Heap::GCTimeInMicros(Space space) const {
   if (space == kNew) {
     return new_space_.gc_time_micros();
@@ -851,6 +863,14 @@
     old_space_.PrintToJSONObject(object);
   }
 }
+
+void Heap::PrintMemoryUsageJSON(JSONStream* stream) const {
+  JSONObject jsobj(stream);
+  jsobj.AddProperty("type", "MemoryUsage");
+  jsobj.AddProperty64("heapUsage", TotalUsedInWords() * kWordSize);
+  jsobj.AddProperty64("heapCapacity", TotalCapacityInWords() * kWordSize);
+  jsobj.AddProperty64("externalUsage", TotalExternalInWords() * kWordSize);
+}
 #endif  // PRODUCT
 
 void Heap::RecordBeforeGC(GCType type, GCReason reason) {
diff --git a/runtime/vm/heap/heap.h b/runtime/vm/heap/heap.h
index 5ae4083..628ab15 100644
--- a/runtime/vm/heap/heap.h
+++ b/runtime/vm/heap/heap.h
@@ -180,6 +180,10 @@
   int64_t UsedInWords(Space space) const;
   int64_t CapacityInWords(Space space) const;
   int64_t ExternalInWords(Space space) const;
+
+  int64_t TotalUsedInWords() const;
+  int64_t TotalCapacityInWords() const;
+  int64_t TotalExternalInWords() const;
   // Return the amount of GCing in microseconds.
   int64_t GCTimeInMicros(Space space) const;
 
@@ -268,6 +272,10 @@
 #ifndef PRODUCT
   void PrintToJSONObject(Space space, JSONObject* object) const;
 
+  // Returns a JSON object with total memory usage statistics for both new and
+  // old space combined.
+  void PrintMemoryUsageJSON(JSONStream* stream) const;
+
   // The heap map contains the sizes and class ids for the objects in each page.
   void PrintHeapMapToJSONStream(Isolate* isolate, JSONStream* stream) {
     old_space_.PrintHeapMapToJSONStream(isolate, stream);
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index 6be1715..ce8ed3f 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -2288,6 +2288,14 @@
 
   jsobj.AddProperty("_threads", thread_registry_);
 }
+
+void Isolate::PrintMemoryUsageJSON(JSONStream* stream) {
+  if (!FLAG_support_service) {
+    return;
+  }
+  heap()->PrintMemoryUsageJSON(stream);
+}
+
 #endif
 
 void Isolate::set_tag_table(const GrowableObjectArray& value) {
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index 7f003d3..40f0c6f 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -515,6 +515,10 @@
 
 #ifndef PRODUCT
   void PrintJSON(JSONStream* stream, bool ref = true);
+
+  // Creates an object with the total heap memory usage statistics for this
+  // isolate.
+  void PrintMemoryUsageJSON(JSONStream* stream);
 #endif
 
 #if !defined(PRODUCT)
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index 2284222..f08dc42 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -1377,6 +1377,16 @@
   return true;
 }
 
+static const MethodParameter* get_memory_usage_params[] = {
+    ISOLATE_PARAMETER,
+    NULL,
+};
+
+static bool GetMemoryUsage(Thread* thread, JSONStream* js) {
+  thread->isolate()->PrintMemoryUsageJSON(js);
+  return true;
+}
+
 static const MethodParameter* get_scripts_params[] = {
     RUNNABLE_ISOLATE_PARAMETER,
     NULL,
@@ -4876,6 +4886,8 @@
     get_instances_params },
   { "getIsolate", GetIsolate,
     get_isolate_params },
+  { "getMemoryUsage", GetMemoryUsage,
+    get_memory_usage_params },
   { "_getIsolateMetric", GetIsolateMetric,
     get_isolate_metric_params },
   { "_getIsolateMetricList", GetIsolateMetricList,
diff --git a/runtime/vm/service/service.md b/runtime/vm/service/service.md
index 46cec8e..0040087 100644
--- a/runtime/vm/service/service.md
+++ b/runtime/vm/service/service.md
@@ -1,8 +1,8 @@
-# Dart VM Service Protocol 3.15
+# Dart VM Service Protocol 3.16
 
 > Please post feedback to the [observatory-discuss group][discuss-list]
 
-This document describes of _version 3.15_ of the Dart VM Service Protocol. This
+This document describes of _version 3.16_ of the Dart VM Service Protocol. This
 protocol is used to communicate with a running Dart Virtual Machine.
 
 To use the Service Protocol, start the VM with the *--observe* flag.
@@ -31,6 +31,7 @@
   - [evaluateInFrame](#evaluateinframe)
   - [getFlagList](#getflaglist)
   - [getIsolate](#getisolate)
+  - [getMemoryUsage](#getmemoryusage)
   - [getScripts](#getscripts)
   - [getObject](#getobject)
   - [getSourceReport](#getsourcereport)
@@ -75,6 +76,7 @@
   - [Library](#library)
   - [LibraryDependency](#librarydependency)
   - [MapAssociation](#mapassociation)
+  - [MemoryUsage](#memoryusage)
   - [Message](#message)
   - [Null](#null)
   - [Object](#object)
@@ -610,6 +612,20 @@
 
 See [Isolate](#isolate).
 
+### getMemoryUsage
+
+```
+MemoryUsage|Sentinel getMemoryUsage(string isolateId)
+```
+
+The _getMemoryUsage_ RPC is used to lookup an isolate's memory usage
+statistics by its _id_.
+
+If _isolateId_ refers to an isolate which has exited, then the
+_Collected_ [Sentinel](#sentinel) is returned.
+
+See [Isolate](#isolate).
+
 ### getScripts
 
 ```
@@ -2207,6 +2223,31 @@
 }
 ```
 
+### MemoryUsage
+
+```
+class MemoryUsage extends Response {
+  // The amount of non-Dart memory that is retained by Dart objects. For
+  // example, memory associated with Dart objects through APIs such as
+  // Dart_NewWeakPersistentHandle and Dart_NewExternalTypedData.  This usage is
+  // only as accurate as the values supplied to these APIs from the VM embedder or
+  // native extensions. This external memory applies GC pressure, but is separate
+  // from heapUsage and heapCapacity.
+  int externalUsage;
+
+  // The total capacity of the heap in bytes. This is the amount of memory used
+  // by the Dart heap from the perspective of the operating system.
+  int heapCapacity;
+
+  // The current heap memory usage in bytes. Heap usage is always less than or
+  // equal to the heap capacity.
+  int heapUsage;
+}
+```
+
+An _MemoryUsage_ object provides heap usage information for a specific
+isolate at a given point in time.
+
 ### Message
 
 ```
@@ -2765,5 +2806,6 @@
 3.13 | Class 'mixin' field now properly set for kernel transformed mixin applications.
 3.14 | Flag 'profile_period' can now be set at runtime, allowing for the profiler sample rate to be changed while the program is running.
 3.15 | Added `disableBreakpoints` parameter to `invoke`, `evaluate`, and `evaluateInFrame`.
+3.16 | Add 'getMemoryUsage' RPC and 'MemoryUsage' object.
 
 [discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss
diff --git a/runtime/vm/service/service_dev.md b/runtime/vm/service/service_dev.md
index 2fe0098..a440425 100644
--- a/runtime/vm/service/service_dev.md
+++ b/runtime/vm/service/service_dev.md
@@ -1,8 +1,8 @@
-# Dart VM Service Protocol 3.16-dev
+# Dart VM Service Protocol 3.17-dev
 
 > Please post feedback to the [observatory-discuss group][discuss-list]
 
-This document describes of _version 3.16-dev_ of the Dart VM Service Protocol. This
+This document describes of _version 3.17-dev_ of the Dart VM Service Protocol. This
 protocol is used to communicate with a running Dart Virtual Machine.
 
 To use the Service Protocol, start the VM with the *--observe* flag.
@@ -31,6 +31,7 @@
   - [evaluateInFrame](#evaluateinframe)
   - [getFlagList](#getflaglist)
   - [getIsolate](#getisolate)
+  - [getMemoryUsage](#getmemoryusage)
   - [getScripts](#getscripts)
   - [getObject](#getobject)
   - [getSourceReport](#getsourcereport)
@@ -75,6 +76,7 @@
   - [Library](#library)
   - [LibraryDependency](#librarydependency)
   - [MapAssociation](#mapassociation)
+  - [MemoryUsage](#memoryusage)
   - [Message](#message)
   - [Null](#null)
   - [Object](#object)
@@ -610,6 +612,20 @@
 
 See [Isolate](#isolate).
 
+### getMemoryUsage
+
+```
+MemoryUsage|Sentinel getMemoryUsage(string isolateId)
+```
+
+The _getMemoryUsage_ RPC is used to lookup an isolate's memory usage
+statistics by its _id_.
+
+If _isolateId_ refers to an isolate which has exited, then the
+_Collected_ [Sentinel](#sentinel) is returned.
+
+See [Isolate](#isolate).
+
 ### getScripts
 
 ```
@@ -2207,6 +2223,31 @@
 }
 ```
 
+### MemoryUsage
+
+```
+class MemoryUsage extends Response {
+  // The amount of non-Dart memory that is retained by Dart objects. For
+  // example, memory associated with Dart objects through APIs such as
+  // Dart_NewWeakPersistentHandle and Dart_NewExternalTypedData.  This usage is
+  // only as accurate as the values supplied to these APIs from the VM embedder or
+  // native extensions. This external memory applies GC pressure, but is separate
+  // from heapUsage and heapCapacity.
+  int externalUsage;
+
+  // The total capacity of the heap in bytes. This is the amount of memory used
+  // by the Dart heap from the perspective of the operating system.
+  int heapCapacity;
+
+  // The current heap memory usage in bytes. Heap usage is always less than or
+  // equal to the heap capacity.
+  int heapUsage;
+}
+```
+
+An _MemoryUsage_ object provides heap usage information for a specific
+isolate at a given point in time.
+
 ### Message
 
 ```
@@ -2765,5 +2806,6 @@
 3.13 | Class 'mixin' field now properly set for kernel transformed mixin applications.
 3.14 | Flag 'profile_period' can now be set at runtime, allowing for the profiler sample rate to be changed while the program is running.
 3.15 | Added `disableBreakpoints` parameter to `invoke`, `evaluate`, and `evaluateInFrame`.
+3.16 | Add 'getMemoryUsage' RPC and 'MemoryUsage' object.
 
 [discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss