mirror of
https://github.com/dart-lang/sdk
synced 2024-09-20 00:01:59 +00:00
2274ce9b05
Change-Id: I660bbf09c758f88589a10b0334ddd34c0620460d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/275244 Commit-Queue: Lasse Nielsen <lrn@google.com> Reviewed-by: Michael Thomsen <mit@google.com>
395 lines
13 KiB
Dart
395 lines
13 KiB
Dart
// 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.
|
|
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'dart:math';
|
|
|
|
import "vm_service_heap_helper.dart" as helper;
|
|
|
|
late Completer completer;
|
|
|
|
Set<String> files = {};
|
|
|
|
// General idea: Do the once-a-week leak testing script
|
|
// => In this file:
|
|
// * Start the frontend_server
|
|
// * Do a compilation
|
|
// * Pause the VM and do a "leak iteration"
|
|
// * Once the VM has been unpaused, do an invalidation etc and repeat.
|
|
//
|
|
// This script also makes sure to clone flutter gallery,
|
|
// but assumes that flutter has been setup as by the script
|
|
// `tools/bots/flutter/compile_flutter.sh`.
|
|
|
|
Future<void> main(List<String> args) async {
|
|
if (Platform.isWindows) {
|
|
throw "This script cannot run on Windows as it uses non-Windows "
|
|
"assumptions both for the placement of pub packages and the presence "
|
|
"of 'ln' for symbolic links. It has only been tested on Linux but will "
|
|
"probably also work on Mac.";
|
|
}
|
|
|
|
bool quicker = false;
|
|
String? rootPath;
|
|
|
|
for (String arg in args) {
|
|
if (arg == "--quicker") {
|
|
quicker = true;
|
|
} else if (arg.startsWith("--path=")) {
|
|
rootPath = arg.substring("--path=".length);
|
|
} else {
|
|
throw "Unknown argument '$arg'";
|
|
}
|
|
}
|
|
|
|
if (rootPath == null) {
|
|
throw "No path given. Pass with --path=<path>";
|
|
}
|
|
|
|
Directory patchedSdk = new Directory("$rootPath/flutter_patched_sdk/");
|
|
if (!patchedSdk.existsSync()) {
|
|
throw "Directory $patchedSdk doesn't exist.";
|
|
}
|
|
Uri frontendServerStarter = Platform.script
|
|
.resolve("../../frontend_server/bin/frontend_server_starter.dart");
|
|
if (!new File.fromUri(frontendServerStarter).existsSync()) {
|
|
throw "File not found: $frontendServerStarter";
|
|
}
|
|
|
|
Directory gallery = new Directory("$rootPath/gallery");
|
|
if (!gallery.existsSync()) {
|
|
print("Gallery not found... Attempting to clone via git.");
|
|
// git clone https://github.com/flutter/gallery.git
|
|
Process process = await Process.start("git", [
|
|
"clone",
|
|
"https://github.com/flutter/gallery.git",
|
|
"$rootPath/gallery"
|
|
]);
|
|
process.stdout
|
|
.transform(utf8.decoder)
|
|
.transform(new LineSplitter())
|
|
.listen((line) {
|
|
print("git stdout> $line");
|
|
});
|
|
process.stderr
|
|
.transform(utf8.decoder)
|
|
.transform(new LineSplitter())
|
|
.listen((line) {
|
|
print("git stderr> $line");
|
|
});
|
|
int processExitCode = await process.exitCode;
|
|
print("Exit code from git: $processExitCode");
|
|
|
|
process = await Process.start("../flutter/bin/flutter", ["pub", "get"],
|
|
workingDirectory: "$rootPath/gallery/");
|
|
process.stdout
|
|
.transform(utf8.decoder)
|
|
.transform(new LineSplitter())
|
|
.listen((line) {
|
|
print("flutter stdout> $line");
|
|
});
|
|
process.stderr
|
|
.transform(utf8.decoder)
|
|
.transform(new LineSplitter())
|
|
.listen((line) {
|
|
print("flutter stderr> $line");
|
|
});
|
|
processExitCode = await process.exitCode;
|
|
print("Exit code from flutter: $processExitCode");
|
|
|
|
// Attempt to hack around strings being truncated to 128 bytes in heap dumps
|
|
// https://github.com/dart-lang/sdk/blob/c59cdee365b94ce066344840f9e3412d642019b5/runtime/vm/object_graph.cc#L809
|
|
// (pub paths can become too long, so two distinct files will look to have
|
|
// the same url and thus give a false positive).
|
|
Uri pubDirUri = Uri.parse("file://${Platform.environment['HOME']}/"
|
|
".pub-cache/hosted/pub.dev/");
|
|
Directory pubDir = new Directory.fromUri(pubDirUri);
|
|
if (!pubDir.existsSync()) {
|
|
File galleryPackageConfig =
|
|
new File("$rootPath/gallery/.dart_tool/package_config.json");
|
|
String? packagesData;
|
|
if (galleryPackageConfig.existsSync()) {
|
|
packagesData = galleryPackageConfig.readAsStringSync();
|
|
}
|
|
|
|
throw "Expected to find $pubDir but didn't.\n"
|
|
"Content of packages file (${packagesData.runtimeType}): "
|
|
"$packagesData";
|
|
}
|
|
|
|
File galleryPackageConfig =
|
|
new File("$rootPath/gallery/.dart_tool/package_config.json");
|
|
if (!galleryPackageConfig.existsSync()) {
|
|
throw "Didn't find $galleryPackageConfig";
|
|
}
|
|
String data = galleryPackageConfig.readAsStringSync();
|
|
data = data.replaceAll(pubDirUri.toString(), "../pub/");
|
|
galleryPackageConfig.writeAsStringSync(data);
|
|
|
|
process = await Process.start("ln", ["-s", pubDir.path, "pub"],
|
|
workingDirectory: "$rootPath/gallery/");
|
|
process.stdout
|
|
.transform(utf8.decoder)
|
|
.transform(new LineSplitter())
|
|
.listen((line) {
|
|
print("ln stdout> $line");
|
|
});
|
|
process.stderr
|
|
.transform(utf8.decoder)
|
|
.transform(new LineSplitter())
|
|
.listen((line) {
|
|
print("ln stderr> $line");
|
|
});
|
|
processExitCode = await process.exitCode;
|
|
print("Exit code from ln: $processExitCode");
|
|
}
|
|
|
|
File packageConfig =
|
|
new File("$rootPath/gallery/.dart_tool/package_config.json");
|
|
if (!packageConfig.existsSync()) {
|
|
throw "Didn't find $packageConfig";
|
|
}
|
|
|
|
List<helper.Interest> interests = <helper.Interest>[];
|
|
interests.add(new helper.Interest(
|
|
Uri.parse("package:kernel/ast.dart"),
|
|
"Library",
|
|
["fileUri"],
|
|
));
|
|
helper.VMServiceHeapHelperSpecificExactLeakFinder heapHelper =
|
|
new helper.VMServiceHeapHelperSpecificExactLeakFinder(
|
|
interests: interests,
|
|
prettyPrints: [
|
|
new helper.Interest(
|
|
Uri.parse("package:kernel/ast.dart"),
|
|
"Library",
|
|
["fileUri", "libraryIdForTesting"],
|
|
),
|
|
],
|
|
throwOnPossibleLeak: true,
|
|
);
|
|
|
|
print("About to run with "
|
|
"quicker = $quicker; "
|
|
"path = $rootPath; "
|
|
"...");
|
|
|
|
List<String> processArgs = [
|
|
"--disable_dart_dev",
|
|
"--disable-service-auth-codes",
|
|
frontendServerStarter.toString(),
|
|
"--sdk-root",
|
|
"$rootPath/flutter_patched_sdk/",
|
|
"--incremental",
|
|
"--target=flutter",
|
|
"--output-dill",
|
|
"$rootPath/flutter_server_tmp.dill",
|
|
"--packages",
|
|
"$rootPath/gallery/.dart_tool/package_config.json",
|
|
"-Ddart.vm.profile=false",
|
|
"-Ddart.vm.product=false",
|
|
"--enable-asserts",
|
|
"--track-widget-creation",
|
|
"--initialize-from-dill",
|
|
"$rootPath/cache.dill",
|
|
];
|
|
|
|
await heapHelper.start(processArgs,
|
|
stdoutReceiver: (s) {
|
|
if (s.startsWith("+")) {
|
|
files.add(s.substring(1));
|
|
} else if (s.startsWith("-")) {
|
|
files.remove(s.substring(1));
|
|
} else {
|
|
List<String> split = s.split(" ");
|
|
if (int.tryParse(split.last) != null &&
|
|
split[split.length - 2].endsWith(".dill")) {
|
|
// e.g. something like "filename.dill 0" for output file and 0
|
|
// errors.
|
|
completer.complete();
|
|
} else {
|
|
print("out> $s");
|
|
}
|
|
}
|
|
},
|
|
stderrReceiver: (s) => print("err> $s"));
|
|
|
|
Stopwatch stopwatch = new Stopwatch()..start();
|
|
await sendAndWait(heapHelper.process, ['compile package:gallery/main.dart']);
|
|
print("First compile took ${stopwatch.elapsedMilliseconds} ms");
|
|
await pauseAndWait(heapHelper);
|
|
|
|
await recompileAndWait(heapHelper.process, "package:gallery/main.dart", []);
|
|
await accept(heapHelper);
|
|
await sendAndWaitSetSelection(heapHelper.process);
|
|
await sendAndWaitToObjectForSourceLocation(heapHelper.process);
|
|
await sendAndWaitToObject(heapHelper.process);
|
|
await pauseAndWait(heapHelper);
|
|
|
|
print("Knows about ${files.length} files...");
|
|
List<String> listFiles = new List<String>.from(files);
|
|
int iteration = 0;
|
|
for (String s in listFiles) {
|
|
print("On iteration ${iteration++} / ${listFiles.length}");
|
|
print(" => Invalidating $s");
|
|
stopwatch.reset();
|
|
await recompileAndWait(
|
|
heapHelper.process, "package:gallery/main.dart", [s]);
|
|
await accept(heapHelper);
|
|
print("Recompile took ${stopwatch.elapsedMilliseconds} ms");
|
|
await sendAndWaitSetSelection(heapHelper.process);
|
|
await sendAndWaitToObjectForSourceLocation(heapHelper.process);
|
|
await sendAndWaitToObject(heapHelper.process);
|
|
if (quicker) {
|
|
if (iteration % 10 == 0) {
|
|
await pauseAndWait(heapHelper);
|
|
}
|
|
} else {
|
|
await pauseAndWait(heapHelper);
|
|
}
|
|
}
|
|
if (quicker) {
|
|
await pauseAndWait(heapHelper);
|
|
}
|
|
|
|
// We should now be done.
|
|
print("Done!");
|
|
heapHelper.process.kill();
|
|
}
|
|
|
|
Future accept(
|
|
helper.VMServiceHeapHelperSpecificExactLeakFinder heapHelper) async {
|
|
heapHelper.process.stdin.writeln('accept');
|
|
int waits = 0;
|
|
while (!await heapHelper.isIdle()) {
|
|
if (waits > 100) {
|
|
// Waited for at least 10 seconds --- assume there's something wrong.
|
|
throw "Timed out waiting to become idle!";
|
|
}
|
|
await new Future.delayed(new Duration(milliseconds: 100));
|
|
waits++;
|
|
}
|
|
}
|
|
|
|
class Uuid {
|
|
final Random _random = new Random();
|
|
|
|
/// Generate a version 4 (random) uuid. This is a uuid scheme that only uses
|
|
/// random numbers as the source of the generated uuid.
|
|
String generateV4() {
|
|
// Generate xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx / 8-4-4-4-12.
|
|
int special = 8 + _random.nextInt(4);
|
|
|
|
return '${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}-'
|
|
'${_bitsDigits(16, 4)}-'
|
|
'4${_bitsDigits(12, 3)}-'
|
|
'${_printDigits(special, 1)}${_bitsDigits(12, 3)}-'
|
|
'${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}${_bitsDigits(16, 4)}';
|
|
}
|
|
|
|
String _bitsDigits(int bitCount, int digitCount) =>
|
|
_printDigits(_generateBits(bitCount), digitCount);
|
|
|
|
int _generateBits(int bitCount) => _random.nextInt(1 << bitCount);
|
|
|
|
String _printDigits(int value, int count) =>
|
|
value.toRadixString(16).padLeft(count, '0');
|
|
}
|
|
|
|
Future pauseAndWait(
|
|
helper.VMServiceHeapHelperSpecificExactLeakFinder heapHelper) async {
|
|
int prevIterationNumber = heapHelper.iterationNumber;
|
|
await heapHelper.pause();
|
|
|
|
int waits = 0;
|
|
while (heapHelper.iterationNumber == prevIterationNumber) {
|
|
if (waits > 10000) {
|
|
// Waited for at least 1000 seconds --- assume there's something wrong.
|
|
throw "Timed out waiting for the helper iteration number to increase!";
|
|
}
|
|
await new Future.delayed(new Duration(milliseconds: 100));
|
|
waits++;
|
|
}
|
|
}
|
|
|
|
Future recompileAndWait(
|
|
Process _server, String what, List<String> invalidates) async {
|
|
String inputKey = Uuid().generateV4();
|
|
List<String> data = ['recompile $what $inputKey'];
|
|
invalidates.forEach(data.add);
|
|
data.add('$inputKey');
|
|
await sendAndWait(_server, data);
|
|
}
|
|
|
|
Future sendAndWait(Process _server, List<String> data) async {
|
|
completer = new Completer();
|
|
data.forEach(_server.stdin.writeln);
|
|
await completer.future;
|
|
}
|
|
|
|
Future sendAndWaitDebugDidSendFirstFrameEvent(Process _server) async {
|
|
String inputKey = Uuid().generateV4();
|
|
await sendAndWait(_server, [
|
|
/* 'compile-expression' <boundarykey> */ 'compile-expression $inputKey',
|
|
/* expression */ 'WidgetsBinding.instance.debugDidSendFirstFrameEvent',
|
|
/* no definitions */
|
|
/* <boundarykey> */ inputKey,
|
|
/* no type-definitions */
|
|
/* <boundarykey> */ inputKey,
|
|
/* libraryUri */ 'package:flutter/src/widgets/binding.dart',
|
|
/* class */ '',
|
|
/* isStatic */ 'true'
|
|
]);
|
|
}
|
|
|
|
Future sendAndWaitSetSelection(Process _server) async {
|
|
String inputKey = Uuid().generateV4();
|
|
await sendAndWait(_server, [
|
|
/* 'compile-expression' <boundarykey> */ 'compile-expression $inputKey',
|
|
/* expression */ 'WidgetInspectorService.instance.setSelection('
|
|
'arg1, "dummy_68")',
|
|
/* definition #1 */ 'arg1',
|
|
/* <boundarykey> */ inputKey,
|
|
/* no type-definitions */
|
|
/* <boundarykey> */ inputKey,
|
|
/* libraryUri */ 'package:flutter/src/widgets/widget_inspector.dart',
|
|
/* class */ '',
|
|
/* isStatic */ 'true'
|
|
]);
|
|
}
|
|
|
|
Future sendAndWaitToObject(Process _server) async {
|
|
String inputKey = Uuid().generateV4();
|
|
await sendAndWait(_server, [
|
|
/* 'compile-expression' <boundarykey> */ 'compile-expression $inputKey',
|
|
/* expression */ 'WidgetInspectorService.instance.toObject('
|
|
'"inspector-836", "tree_112")',
|
|
/* no definitions */
|
|
/* <boundarykey> */ inputKey,
|
|
/* no type-definitions */
|
|
/* <boundarykey> */ inputKey,
|
|
/* libraryUri */ 'package:flutter/src/widgets/widget_inspector.dart',
|
|
/* class */ '',
|
|
/* isStatic */ 'true'
|
|
]);
|
|
}
|
|
|
|
Future sendAndWaitToObjectForSourceLocation(Process _server) async {
|
|
String inputKey = Uuid().generateV4();
|
|
await sendAndWait(_server, [
|
|
/* 'compile-expression' <boundarykey> */ 'compile-expression $inputKey',
|
|
/* expression */ 'WidgetInspectorService.instance.'
|
|
'toObjectForSourceLocation("inspector-607", "tree_112")',
|
|
/* no definitions */
|
|
/* <boundarykey> */ inputKey,
|
|
/* no type-definitions */
|
|
/* <boundarykey> */ inputKey,
|
|
/* libraryUri */ 'package:flutter/src/widgets/widget_inspector.dart',
|
|
/* class */ '',
|
|
/* isStatic */ 'true'
|
|
]);
|
|
}
|