[gardening] Attempt to fix flakily timing out service{,_2}/get_vm_timeline_rpc_test

We launch the subprocess with --complete-timeline which will collect and
buffer all timeline events in-memory. This works ok if we don't have too
many events and if we have enough memory.

On ia32 in particular the JIT's kernel-service runs from kernel file
(not from an app-jit trained snapshot). That means it's slow and
possibly produces many more timeline events since kernel-service isolate
has to JIT all of the CFE in order to compile the test script.

On ia32 we only have 2 or 3 GB of memory available. So this could lead
to OOMs to due the timeline buffer getting too large.

(See issue below for more information)

This is an attempt to fix that by first compiling the testee to kernel,
so the subprocess can be run immediately from kernel instead of first
needing to compile via CFE.

Issue https://github.com/dart-lang/sdk/issues/43504

TEST=Tries to make flakily timing out less flaky.

Change-Id: I46a674ce7a8c44a716a543c73703d40f690f396e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/210723
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Tess Strickland <sstrickl@google.com>
diff --git a/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart b/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart
index d0bfa3e..5626076 100644
--- a/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart
+++ b/runtime/observatory/tests/service/get_vm_timeline_rpc_test.dart
@@ -188,8 +188,14 @@
       .where((String arg) => !arg.contains('optimization-counter-threshold'))
       .toList();
 
+  // We first compile the testee to kernel and run the subprocess on the kernel
+  // file. That avoids cases where the testee has to run a lot of code in the
+  // kernel-isolate (e.g. due to ia32's kernel-service not being app-jit
+  // trained). We do that because otherwise the --complete-timeline will collect
+  // a lot of data, possibly leading to OOMs or timeouts.
   await runVMTests(args, tests,
       testeeBefore: primeTimeline,
       extraArgs: ['--complete-timeline'],
-      executableArgs: executableArgs);
+      executableArgs: executableArgs,
+      compileToKernelFirst: true);
 }
diff --git a/runtime/observatory/tests/service/test_helper.dart b/runtime/observatory/tests/service/test_helper.dart
index 0369d53..487c098 100644
--- a/runtime/observatory/tests/service/test_helper.dart
+++ b/runtime/observatory/tests/service/test_helper.dart
@@ -7,8 +7,10 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
+
 import 'package:dds/dds.dart';
 import 'package:observatory/service_io.dart';
+import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 import 'service_test_common.dart';
 export 'service_test_common.dart' show DDSTest, IsolateTest, VMTest;
@@ -94,7 +96,8 @@
   final _processCompleter = Completer<void>();
   bool killedByTester = false;
 
-  _ServiceTesteeLauncher() : args = [Platform.script.toFilePath()] {}
+  _ServiceTesteeLauncher({String? script})
+      : args = [script ?? Platform.script.toFilePath()] {}
 
   // Spawn the testee process.
   Future<Process> _spawnProcess(
@@ -343,6 +346,7 @@
     bool testeeControlsServer: false,
     bool enableDds: true,
     bool enableService: true,
+    bool compileToKernelFirst: false,
     int port = 0,
   }) {
     if (executableArgs == null) {
@@ -352,6 +356,7 @@
     late WebSocketVM vm;
     late _ServiceTesteeLauncher process;
     bool testsDone = false;
+    final tempDir = Directory.systemTemp.createTempSync('testee_dill');
 
     ignoreLateException(Function f) async {
       try {
@@ -369,7 +374,26 @@
     setUp(
       () => ignoreLateException(
         () async {
-          process = _ServiceTesteeLauncher();
+          String testeePath = Platform.script.toFilePath();
+          if (compileToKernelFirst && testeePath.endsWith('.dart')) {
+            final testeePathDill = path.join(tempDir.path, 'testee.dill');
+            final ProcessResult result =
+                await Process.run(Platform.executable, [
+              '--snapshot-kind=kernel',
+              '--snapshot=$testeePathDill',
+              ...Platform.executableArguments,
+              testeePath,
+            ]);
+            if (result.exitCode != 0) {
+              throw 'Failed to compile testee to kernel:\n'
+                  'stdout: ${result.stdout}\n'
+                  'stderr: ${result.stderr}\n'
+                  'exitCode: ${result.exitCode}\n';
+            }
+            testeePath = testeePathDill;
+          }
+
+          process = _ServiceTesteeLauncher(script: testeePath);
           await process
               .launch(
                   pause_on_start,
@@ -611,6 +635,7 @@
     bool enable_service_port_fallback: false,
     bool enableDds: true,
     bool enableService: true,
+    bool compileToKernelFirst: false,
     int port = 0,
     List<String>? extraArgs,
     List<String>? executableArgs}) async {
@@ -633,6 +658,7 @@
       enable_service_port_fallback: enable_service_port_fallback,
       enableDds: enableDds,
       enableService: enableService,
+      compileToKernelFirst: compileToKernelFirst,
       port: port,
     );
   }
diff --git a/runtime/observatory_2/tests/service_2/get_vm_timeline_rpc_test.dart b/runtime/observatory_2/tests/service_2/get_vm_timeline_rpc_test.dart
index 94e2654..0f8e1de 100644
--- a/runtime/observatory_2/tests/service_2/get_vm_timeline_rpc_test.dart
+++ b/runtime/observatory_2/tests/service_2/get_vm_timeline_rpc_test.dart
@@ -188,8 +188,14 @@
       .where((String arg) => !arg.contains('optimization-counter-threshold'))
       .toList();
 
+  // We first compile the testee to kernel and run the subprocess on the kernel
+  // file. That avoids cases where the testee has to run a lot of code in the
+  // kernel-isolate (e.g. due to ia32's kernel-service not being app-jit
+  // trained). We do that because otherwise the --complete-timeline will collect
+  // a lot of data, possibly leading to OOMs or timeouts.
   await runVMTests(args, tests,
       testeeBefore: primeTimeline,
       extraArgs: ['--complete-timeline'],
-      executableArgs: executableArgs);
+      executableArgs: executableArgs,
+      compileToKernelFirst: true);
 }
diff --git a/runtime/observatory_2/tests/service_2/test_helper.dart b/runtime/observatory_2/tests/service_2/test_helper.dart
index 570e830..86e7b13 100644
--- a/runtime/observatory_2/tests/service_2/test_helper.dart
+++ b/runtime/observatory_2/tests/service_2/test_helper.dart
@@ -9,8 +9,10 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
+
 import 'package:dds/dds.dart';
 import 'package:observatory_2/service_io.dart';
+import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 import 'service_test_common.dart';
 export 'service_test_common.dart' show DDSTest, IsolateTest, VMTest;
@@ -96,7 +98,8 @@
   final _processCompleter = Completer<void>();
   bool killedByTester = false;
 
-  _ServiceTesteeLauncher() : args = [Platform.script.toFilePath()] {}
+  _ServiceTesteeLauncher({String script})
+      : args = [script ?? Platform.script.toFilePath()] {}
 
   // Spawn the testee process.
   Future<Process> _spawnProcess(
@@ -341,6 +344,7 @@
     bool testeeControlsServer: false,
     bool enableDds: true,
     bool enableService: true,
+    bool compileToKernelFirst: false,
     int port = 0,
   }) {
     if (executableArgs == null) {
@@ -350,6 +354,7 @@
     WebSocketVM vm;
     _ServiceTesteeLauncher process;
     bool testsDone = false;
+    final tempDir = Directory.systemTemp.createTempSync('testee_dill');
 
     ignoreLateException(Function f) async {
       try {
@@ -367,7 +372,26 @@
     setUp(
       () => ignoreLateException(
         () async {
-          process = _ServiceTesteeLauncher();
+          String testeePath = Platform.script.toFilePath();
+          if (compileToKernelFirst && testeePath.endsWith('.dart')) {
+            final testeePathDill = path.join(tempDir.path, 'testee.dill');
+            final ProcessResult result =
+                await Process.run(Platform.executable, [
+              '--snapshot-kind=kernel',
+              '--snapshot=$testeePathDill',
+              ...Platform.executableArguments,
+              testeePath,
+            ]);
+            if (result.exitCode != 0) {
+              throw 'Failed to compile testee to kernel:\n'
+                  'stdout: ${result.stdout}\n'
+                  'stderr: ${result.stderr}\n'
+                  'exitCode: ${result.exitCode}\n';
+            }
+            testeePath = testeePathDill;
+          }
+
+          process = _ServiceTesteeLauncher(script: testeePath);
           await process
               .launch(
                   pause_on_start,
@@ -411,6 +435,7 @@
             await dds?.shutdown();
           }
           process.requestExit();
+          tempDir.deleteSync(recursive: true);
         },
       ),
     );
@@ -609,6 +634,7 @@
     bool enable_service_port_fallback: false,
     bool enableDds: true,
     bool enableService: true,
+    bool compileToKernelFirst: false,
     int port = 0,
     List<String> extraArgs,
     List<String> executableArgs}) async {
@@ -631,6 +657,7 @@
       enable_service_port_fallback: enable_service_port_fallback,
       enableDds: enableDds,
       enableService: enableService,
+      compileToKernelFirst: compileToKernelFirst,
       port: port,
     );
   }