[CFE] Update tool for investigation of the runtime via service protocol

Change-Id: I670f97f135c8cae37b6e07e95e6de69e88a1caf4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/136586
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt
index 55c745c..4f64f3a 100644
--- a/pkg/front_end/test/spell_checking_list_tests.txt
+++ b/pkg/front_end/test/spell_checking_list_tests.txt
@@ -358,6 +358,8 @@
 resource
 respected
 response
+retaining
+retainingpath
 retains
 rev
 risky
diff --git a/pkg/front_end/test/vm_service_heap_finder.dart b/pkg/front_end/test/vm_service_heap_finder.dart
index f199b1e..6535c13 100644
--- a/pkg/front_end/test/vm_service_heap_finder.dart
+++ b/pkg/front_end/test/vm_service_heap_finder.dart
@@ -8,27 +8,46 @@
   Foo(this.x, this.y);
 }
 
-main() async {
+main(List<String> args) async {
+  String connectTo;
+  String classToFind;
+  String whatToDo;
+  for (String arg in args) {
+    if (arg.startsWith("--url=")) {
+      connectTo = arg.substring("--url=".length);
+    } else if (arg.startsWith("--find=")) {
+      classToFind = arg.substring("--find=".length);
+    } else if (arg.startsWith("--action=")) {
+      whatToDo = arg.substring("--action=".length);
+    }
+  }
   List<Foo> foos = [];
   foos.add(new Foo("hello", 42));
   foos.add(new Foo("world", 43));
   foos.add(new Foo("!", 44));
-  String connectTo = ask("Connect to");
-  VMServiceHeapHelperBase vm = VMServiceHeapHelperBase();
-  await vm.connect(Uri.parse(connectTo));
-  String isolateId = await vm.getIsolateId();
-  String classToFind = ask("Find what class");
-  await vm.printAllocationProfile(isolateId, filter: classToFind);
-  String fieldToFilter = ask("Filter on what field");
-  Set<String> fieldValues = {};
-  while (true) {
-    String fieldValue = ask("Look for value in field (empty to stop)");
-    if (fieldValue == "") break;
-    fieldValues.add(fieldValue);
-  }
 
-  await vm.filterAndPrintInstances(
-      isolateId, classToFind, fieldToFilter, fieldValues);
+  if (connectTo == null) connectTo = ask("Connect to");
+  VMServiceHeapHelperBase vm = VMServiceHeapHelperBase();
+  await vm.connect(Uri.parse(connectTo.trim()));
+  String isolateId = await vm.getIsolateId();
+  if (classToFind == null) classToFind = ask("Find what class");
+
+  if (whatToDo == null) whatToDo = ask("What to do? (filter/retainingpath)");
+  if (whatToDo == "retainingpath") {
+    await vm.printRetainingPaths(isolateId, classToFind);
+  } else {
+    await vm.printAllocationProfile(isolateId, filter: classToFind);
+    String fieldToFilter = ask("Filter on what field");
+    Set<String> fieldValues = {};
+    while (true) {
+      String fieldValue = ask("Look for value in field (empty to stop)");
+      if (fieldValue == "") break;
+      fieldValues.add(fieldValue);
+    }
+
+    await vm.filterAndPrintInstances(
+        isolateId, classToFind, fieldToFilter, fieldValues);
+  }
 
   await vm.disconnect();
   print("Disconnect done!");
diff --git a/pkg/front_end/test/vm_service_heap_helper.dart b/pkg/front_end/test/vm_service_heap_helper.dart
index e23bdbd..eaca3de 100644
--- a/pkg/front_end/test/vm_service_heap_helper.dart
+++ b/pkg/front_end/test/vm_service_heap_helper.dart
@@ -13,8 +13,9 @@
   VMServiceHeapHelperBase();
 
   Future connect(Uri observatoryUri) async {
-    String wsUriString =
-        'ws://${observatoryUri.authority}${observatoryUri.path}ws';
+    String path = observatoryUri.path;
+    if (!path.endsWith("/")) path += "/";
+    String wsUriString = 'ws://${observatoryUri.authority}${path}ws';
     _serviceClient = await vmService.vmServiceConnectUri(wsUriString,
         log: const StdOutLog());
   }
@@ -159,6 +160,41 @@
     print("Done!");
   }
 
+  Future<void> printRetainingPaths(String isolateId, String filter) async {
+    await _waitUntilIsolateIsRunnable(isolateId);
+    vmService.AllocationProfile allocationProfile =
+        await _serviceClient.getAllocationProfile(isolateId);
+    for (vmService.ClassHeapStats member in allocationProfile.members) {
+      if (member.classRef.name != filter) continue;
+      vmService.Class c =
+          await _serviceClient.getObject(isolateId, member.classRef.id);
+      print("Found ${c.name} (location: ${c.location})");
+      print("${member.classRef.name}: "
+          "(instancesCurrent: ${member.instancesCurrent})");
+      print("");
+
+      vmService.InstanceSet instances = await _serviceClient.getInstances(
+          isolateId, member.classRef.id, 10000);
+      print(" => Got ${instances.instances.length} instances");
+      print("");
+
+      for (vmService.ObjRef instance in instances.instances) {
+        var receivedObject =
+            await _serviceClient.getObject(isolateId, instance.id);
+        print("Instance: $receivedObject");
+        vmService.RetainingPath retainingPath =
+            await _serviceClient.getRetainingPath(isolateId, instance.id, 1000);
+        print("Retaining path: (length ${retainingPath.length}");
+        for (int i = 0; i < retainingPath.elements.length; i++) {
+          print("  [$i] = ${retainingPath.elements[i]}");
+        }
+
+        print("");
+      }
+    }
+    print("Done!");
+  }
+
   Future<String> getIsolateId() async {
     vmService.VM vm = await _serviceClient.getVM();
     if (vm.isolates.length != 1) {