// Copyright (c) 2020, 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. // @dart = 2.9 import "dart:convert"; import "dart:io"; import "package:vm_service/vm_service.dart" as vmService; import "package:vm_service/vm_service_io.dart" as vmService; export "package:vm_service/vm_service.dart"; export "package:vm_service/vm_service_io.dart"; class VMServiceHelper { vmService.VmService _serviceClient; vmService.VmService get serviceClient => _serviceClient; VMServiceHelper(); Future connect(Uri observatoryUri) async { String path = observatoryUri.path; if (!path.endsWith("/")) path += "/"; String wsUriString = 'ws://${observatoryUri.authority}${path}ws'; _serviceClient = await vmService.vmServiceConnectUri(wsUriString, log: const StdOutLog()); } Future disconnect() async { await _serviceClient.dispose(); } Future waitUntilPaused(String isolateId) async { int nulls = 0; while (true) { bool result = await isPaused(isolateId); if (result == null) { nulls++; if (nulls > 5) { // We've now asked for the isolate 5 times and in all cases gotten // `Sentinel`. Most likely things aren't working for whatever reason. return false; } } else if (result) { return true; } else { await Future.delayed(const Duration(milliseconds: 100)); } } } Future isPaused(String isolateId) async { dynamic tmp = await _serviceClient.getIsolate(isolateId); if (tmp is vmService.Isolate) { vmService.Isolate isolate = tmp; if (isolate.pauseEvent.kind != "Resume") return true; return false; } return null; } Future isPausedAtStart(String isolateId) async { dynamic tmp = await _serviceClient.getIsolate(isolateId); if (tmp is vmService.Isolate) { vmService.Isolate isolate = tmp; return isolate.pauseEvent.kind == "PauseStart"; } return false; } Future isPausedAtExit(String isolateId) async { dynamic tmp = await _serviceClient.getIsolate(isolateId); if (tmp is vmService.Isolate) { vmService.Isolate isolate = tmp; return isolate.pauseEvent.kind == "PauseExit"; } return false; } Future forceGC(String isolateId) async { await waitUntilIsolateIsRunnable(isolateId); int expectGcAfter = new DateTime.now().millisecondsSinceEpoch; while (true) { vmService.AllocationProfile allocationProfile; try { allocationProfile = await _serviceClient.getAllocationProfile(isolateId, gc: true); } catch (e) { print(e.runtimeType); rethrow; } if (allocationProfile.dateLastServiceGC != null && allocationProfile.dateLastServiceGC >= expectGcAfter) { return allocationProfile; } } } Future isIsolateRunnable(String isolateId) async { dynamic tmp = await _serviceClient.getIsolate(isolateId); if (tmp is vmService.Isolate) { vmService.Isolate isolate = tmp; return isolate.runnable; } return null; } Future waitUntilIsolateIsRunnable(String isolateId) async { int nulls = 0; while (true) { bool result = await isIsolateRunnable(isolateId); if (result == null) { nulls++; if (nulls > 5) { // We've now asked for the isolate 5 times and in all cases gotten // `Sentinel`. Most likely things aren't working for whatever reason. return; } } else if (result) { return; } else { await Future.delayed(const Duration(milliseconds: 100)); } } } Future getIsolateId() async { vmService.VM vm = await _serviceClient.getVM(); if (vm.isolates.length != 1) { throw "Expected 1 isolate, got ${vm.isolates.length}"; } vmService.IsolateRef isolateRef = vm.isolates.single; return isolateRef.id; } } class StdOutLog implements vmService.Log { const StdOutLog(); @override void severe(String message) { print("> SEVERE: $message"); } @override void warning(String message) { print("> WARNING: $message"); } } abstract class LaunchingVMServiceHelper extends VMServiceHelper { Process _process; Process get process => _process; bool _started = false; void start(List scriptAndArgs, {void stdinReceiver(String line), void stderrReceiver(String line)}) async { if (_started) throw "Already started"; _started = true; _process = await Process.start( Platform.resolvedExecutable, ["--pause_isolates_on_start", "--enable-vm-service=0"] ..addAll(scriptAndArgs)); _process.stdout .transform(utf8.decoder) .transform(new LineSplitter()) .listen((line) { const kObservatoryListening = 'Observatory listening on '; if (line.startsWith(kObservatoryListening)) { Uri observatoryUri = Uri.parse(line.substring(kObservatoryListening.length)); _setupAndRun(observatoryUri).catchError((e, st) { // Manually kill the process or it will leak, // see http://dartbug.com/42918 killProcess(); // This seems to rethrow. throw e; }); } if (stdinReceiver != null) { stdinReceiver(line); } else { stdout.writeln("> $line"); } }); _process.stderr .transform(utf8.decoder) .transform(new LineSplitter()) .listen((line) { if (stderrReceiver != null) { stderrReceiver(line); } else { stderr.writeln("> $line"); } }); // ignore: unawaited_futures _process.exitCode.then((value) { processExited(value); }); } void processExited(int exitCode) {} void killProcess() { _process.kill(); } Future _setupAndRun(Uri observatoryUri) async { await connect(observatoryUri); await run(); } Future run(); }