mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 22:41:41 +00:00
[CFE] Add first stab at weekly leak test
Change-Id: Ib0307a4d82414e8387f65a9010a6f0f91827c3bb Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/157962 Commit-Queue: Jens Johansen <jensj@google.com> Reviewed-by: Alexander Thomas <athom@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
parent
5ad3540b54
commit
13de7e7560
395
pkg/front_end/test/flutter_gallery_leak_tester.dart
Normal file
395
pkg/front_end/test/flutter_gallery_leak_tester.dart
Normal file
|
@ -0,0 +1,395 @@
|
|||
// 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;
|
||||
|
||||
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`.
|
||||
|
||||
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;
|
||||
bool alternativeInvalidation = false;
|
||||
String rootPath;
|
||||
|
||||
for (String arg in args) {
|
||||
if (arg == "--quicker") {
|
||||
quicker = true;
|
||||
} else if (arg == "--alternativeInvalidation") {
|
||||
alternativeInvalidation = 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.dartlang.org/");
|
||||
Directory pubDir = new Directory.fromUri(pubDirUri);
|
||||
if (!pubDir.existsSync()) throw "Expected to find $pubDir";
|
||||
File galleryDotPackages = new File("$rootPath/gallery/.packages");
|
||||
if (!galleryDotPackages.existsSync()) {
|
||||
throw "Didn't find $galleryDotPackages";
|
||||
}
|
||||
String data = galleryDotPackages.readAsStringSync();
|
||||
data = data.replaceAll(pubDirUri.toString(), "pub/");
|
||||
galleryDotPackages.writeAsStringSync(data);
|
||||
|
||||
File galleryPackageConfig =
|
||||
new File("$rootPath/gallery/.dart_tool/package_config.json");
|
||||
if (!galleryPackageConfig.existsSync()) {
|
||||
throw "Didn't find $galleryPackageConfig";
|
||||
}
|
||||
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 galleryDotPackages = new File("$rootPath/gallery/.packages");
|
||||
if (!galleryDotPackages.existsSync()) {
|
||||
throw "Didn't find $galleryDotPackages";
|
||||
}
|
||||
|
||||
List<helper.Interest> interests = new List<helper.Interest>();
|
||||
interests.add(new helper.Interest(
|
||||
Uri.parse("package:kernel/ast.dart"), "Library", ["fileUri"]));
|
||||
helper.VMServiceHeapHelperSpecificExactLeakFinder heapHelper =
|
||||
new helper.VMServiceHeapHelperSpecificExactLeakFinder(
|
||||
interests,
|
||||
[
|
||||
new helper.Interest(Uri.parse("package:kernel/ast.dart"), "Library",
|
||||
["fileUri", "_libraryIdString"]),
|
||||
],
|
||||
true,
|
||||
false);
|
||||
|
||||
print("About to run with "
|
||||
"quicker = $quicker; "
|
||||
"alternativeInvalidation = $alternativeInvalidation; "
|
||||
"path = $rootPath; "
|
||||
"...");
|
||||
|
||||
List<String> processArgs = [
|
||||
"--disable_dart_dev",
|
||||
"--disable-service-auth-codes",
|
||||
frontendServerStarter.toString(),
|
||||
"--sdk-root",
|
||||
"$rootPath/flutter_patched_sdk/",
|
||||
"--incremental",
|
||||
"--target=flutter",
|
||||
"--debugger-module-names",
|
||||
"-Ddart.developer.causal_async_stacks=true",
|
||||
"--output-dill",
|
||||
"$rootPath/flutter_server_tmp.dill",
|
||||
"--packages",
|
||||
"$rootPath/gallery/.packages",
|
||||
"-Ddart.vm.profile=false",
|
||||
"-Ddart.vm.product=false",
|
||||
"--bytecode-options=source-positions,local-var-info,debugger-stops,"
|
||||
"instance-field-initializers,keep-unreachable-code,"
|
||||
"avoid-closure-call-instructions",
|
||||
"--enable-asserts",
|
||||
"--track-widget-creation",
|
||||
"--initialize-from-dill",
|
||||
"$rootPath/cache.dill",
|
||||
];
|
||||
if (alternativeInvalidation) {
|
||||
processArgs.add("--enable-experiment=alternative-invalidation-strategy");
|
||||
}
|
||||
|
||||
await heapHelper.start(processArgs,
|
||||
stdinReceiver: (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"));
|
||||
|
||||
await sendAndWait(heapHelper.process, ['compile package:gallery/main.dart']);
|
||||
Stopwatch stopwatch = new Stopwatch()..start();
|
||||
await pauseAndWait(heapHelper);
|
||||
print("First compile took ${stopwatch.elapsedMilliseconds} ms");
|
||||
|
||||
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-defintions */
|
||||
/* <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-defintions */
|
||||
/* <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-defintions */
|
||||
/* <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-defintions */
|
||||
/* <boundarykey> */ inputKey,
|
||||
/* libraryUri */ 'package:flutter/src/widgets/widget_inspector.dart',
|
||||
/* class */ '',
|
||||
/* isStatic */ 'true'
|
||||
]);
|
||||
}
|
|
@ -65,6 +65,7 @@ blorp
|
|||
boo
|
||||
bootstrap
|
||||
bots
|
||||
boundarykey
|
||||
bowtie
|
||||
boz
|
||||
bq
|
||||
|
@ -75,6 +76,7 @@ builddir
|
|||
bulk2
|
||||
bulkcompile
|
||||
c's
|
||||
c59cdee365b94ce066344840f9e3412d642019b
|
||||
ca
|
||||
cafebabe
|
||||
callable
|
||||
|
@ -131,6 +133,7 @@ contract
|
|||
conversion
|
||||
conversions
|
||||
coo
|
||||
costly
|
||||
cov
|
||||
crashes
|
||||
cumulative
|
||||
|
@ -145,11 +148,13 @@ dash
|
|||
dashes
|
||||
day
|
||||
db
|
||||
ddart
|
||||
dds
|
||||
debugger
|
||||
decrease
|
||||
decrements
|
||||
def
|
||||
defintions
|
||||
deleting
|
||||
denylist
|
||||
depended
|
||||
|
@ -185,6 +190,7 @@ dog
|
|||
doo
|
||||
downstream
|
||||
dumping
|
||||
dumps
|
||||
dupe
|
||||
durations
|
||||
dw
|
||||
|
@ -284,6 +290,7 @@ hi
|
|||
hints
|
||||
home
|
||||
hoo
|
||||
hosted
|
||||
hosting
|
||||
hot
|
||||
hotreload
|
||||
|
@ -291,6 +298,7 @@ hunk
|
|||
hurray
|
||||
i'm
|
||||
ia
|
||||
idle
|
||||
ikg
|
||||
illustrate
|
||||
image
|
||||
|
@ -357,6 +365,7 @@ lints
|
|||
linux
|
||||
listening
|
||||
ll
|
||||
ln
|
||||
local1a
|
||||
local1b
|
||||
local1c
|
||||
|
@ -443,6 +452,7 @@ periodically
|
|||
person
|
||||
phrase
|
||||
pink
|
||||
placement
|
||||
places
|
||||
plug
|
||||
pointed
|
||||
|
@ -468,6 +478,7 @@ pv
|
|||
px
|
||||
py
|
||||
python
|
||||
quicker
|
||||
quot
|
||||
quux
|
||||
qux
|
||||
|
@ -512,6 +523,7 @@ sdkroot
|
|||
sdks
|
||||
secondary
|
||||
segment
|
||||
selection
|
||||
sensitive
|
||||
services
|
||||
severe
|
||||
|
@ -561,6 +573,7 @@ superinterface
|
|||
supermixin
|
||||
supplement
|
||||
suspension
|
||||
symbolic
|
||||
t\b\f\u
|
||||
t\u0008\f\u
|
||||
tails
|
||||
|
@ -571,6 +584,7 @@ test3b
|
|||
thereof
|
||||
thing
|
||||
threw
|
||||
timed
|
||||
timeout
|
||||
timer
|
||||
timing
|
||||
|
@ -599,6 +613,7 @@ unassignment
|
|||
unawaited
|
||||
unbreak
|
||||
unpacked
|
||||
unpaused
|
||||
unregistered
|
||||
untransformed
|
||||
untrimmed
|
||||
|
@ -608,13 +623,17 @@ uppercase
|
|||
upward
|
||||
uses8
|
||||
usual
|
||||
uuid
|
||||
val
|
||||
vars
|
||||
verbatim
|
||||
versioning
|
||||
waited
|
||||
waiting
|
||||
waits
|
||||
walt
|
||||
warmup
|
||||
week
|
||||
wherever
|
||||
whiskers
|
||||
wins
|
||||
|
@ -626,5 +645,8 @@ x's
|
|||
xlate
|
||||
xrequired
|
||||
xxx
|
||||
xxxxxxxx
|
||||
xxxxxxxxxxxx
|
||||
y's
|
||||
year
|
||||
yxxx
|
||||
|
|
|
@ -30,6 +30,7 @@ main(List<String> args) async {
|
|||
new helper.Interest(Uri.parse("package:kernel/ast.dart"), "Library",
|
||||
["fileUri", "_libraryIdString"]),
|
||||
],
|
||||
true,
|
||||
true);
|
||||
|
||||
if (args.length > 0 && args[0] == "--dart2js") {
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
// 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:io";
|
||||
import "vm_service_heap_helper.dart";
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
// 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:convert";
|
||||
import "dart:developer";
|
||||
import "dart:io";
|
||||
|
||||
import "package:vm_service/vm_service.dart" as vmService;
|
||||
|
@ -214,10 +217,13 @@ class VMServiceHeapHelperBase {
|
|||
|
||||
abstract class LaunchingVMServiceHeapHelper extends VMServiceHeapHelperBase {
|
||||
Process _process;
|
||||
Process get process => _process;
|
||||
|
||||
bool _started = false;
|
||||
|
||||
void start(List<String> scriptAndArgs) async {
|
||||
void start(List<String> scriptAndArgs,
|
||||
{void stdinReceiver(String line),
|
||||
void stderrReceiver(String line)}) async {
|
||||
if (_started) throw "Already started";
|
||||
_started = true;
|
||||
_process = await Process.start(
|
||||
|
@ -232,15 +238,29 @@ abstract class LaunchingVMServiceHeapHelper extends VMServiceHeapHelperBase {
|
|||
if (line.startsWith(kObservatoryListening)) {
|
||||
Uri observatoryUri =
|
||||
Uri.parse(line.substring(kObservatoryListening.length));
|
||||
_setupAndRun(observatoryUri);
|
||||
_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");
|
||||
}
|
||||
stdout.writeln("> $line");
|
||||
});
|
||||
_process.stderr
|
||||
.transform(utf8.decoder)
|
||||
.transform(new LineSplitter())
|
||||
.listen((line) {
|
||||
stderr.writeln("> $line");
|
||||
if (stderrReceiver != null) {
|
||||
stderrReceiver(line);
|
||||
} else {
|
||||
stderr.writeln("> $line");
|
||||
}
|
||||
});
|
||||
// ignore: unawaited_futures
|
||||
_process.exitCode.then((value) {
|
||||
|
@ -254,7 +274,7 @@ abstract class LaunchingVMServiceHeapHelper extends VMServiceHeapHelperBase {
|
|||
_process.kill();
|
||||
}
|
||||
|
||||
void _setupAndRun(Uri observatoryUri) async {
|
||||
Future _setupAndRun(Uri observatoryUri) async {
|
||||
await connect(observatoryUri);
|
||||
await run();
|
||||
}
|
||||
|
@ -269,9 +289,13 @@ class VMServiceHeapHelperSpecificExactLeakFinder
|
|||
final Map<Uri, Map<String, List<String>>> _prettyPrints =
|
||||
new Map<Uri, Map<String, List<String>>>();
|
||||
final bool throwOnPossibleLeak;
|
||||
final bool tryToFindShortestPathToLeaks;
|
||||
|
||||
VMServiceHeapHelperSpecificExactLeakFinder(List<Interest> interests,
|
||||
List<Interest> prettyPrints, this.throwOnPossibleLeak) {
|
||||
VMServiceHeapHelperSpecificExactLeakFinder(
|
||||
List<Interest> interests,
|
||||
List<Interest> prettyPrints,
|
||||
this.throwOnPossibleLeak,
|
||||
this.tryToFindShortestPathToLeaks) {
|
||||
if (interests.isEmpty) throw "Empty list of interests given";
|
||||
for (Interest interest in interests) {
|
||||
Map<String, List<String>> classToFields = _interests[interest.uri];
|
||||
|
@ -301,28 +325,50 @@ class VMServiceHeapHelperSpecificExactLeakFinder
|
|||
}
|
||||
}
|
||||
|
||||
void pause() async {
|
||||
await _serviceClient.pause(_isolateRef.id);
|
||||
}
|
||||
|
||||
vmService.VM _vm;
|
||||
vmService.IsolateRef _isolateRef;
|
||||
int _iterationNumber;
|
||||
int get iterationNumber => _iterationNumber;
|
||||
|
||||
/// Best effort check if the isolate is idle.
|
||||
Future<bool> isIdle() async {
|
||||
dynamic tmp = await _serviceClient.getIsolate(_isolateRef.id);
|
||||
if (tmp is vmService.Isolate) {
|
||||
vmService.Isolate isolate = tmp;
|
||||
return isolate.pauseEvent.topFrame == null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> run() async {
|
||||
vmService.VM vm = await _serviceClient.getVM();
|
||||
if (vm.isolates.length != 1) {
|
||||
throw "Expected 1 isolate, got ${vm.isolates.length}";
|
||||
_vm = await _serviceClient.getVM();
|
||||
if (_vm.isolates.length != 1) {
|
||||
throw "Expected 1 isolate, got ${_vm.isolates.length}";
|
||||
}
|
||||
vmService.IsolateRef isolateRef = vm.isolates.single;
|
||||
await forceGC(isolateRef.id);
|
||||
_isolateRef = _vm.isolates.single;
|
||||
await forceGC(_isolateRef.id);
|
||||
|
||||
assert(await _isPausedAtStart(isolateRef.id));
|
||||
await _serviceClient.resume(isolateRef.id);
|
||||
assert(await _isPausedAtStart(_isolateRef.id));
|
||||
await _serviceClient.resume(_isolateRef.id);
|
||||
|
||||
int iterationNumber = 1;
|
||||
_iterationNumber = 1;
|
||||
while (true) {
|
||||
await waitUntilPaused(isolateRef.id);
|
||||
print("Iteration: #$iterationNumber");
|
||||
iterationNumber++;
|
||||
await forceGC(isolateRef.id);
|
||||
await waitUntilPaused(_isolateRef.id);
|
||||
print("Iteration: #$_iterationNumber");
|
||||
await forceGC(_isolateRef.id);
|
||||
|
||||
vmService.HeapSnapshotGraph heapSnapshotGraph =
|
||||
await vmService.HeapSnapshotGraph.getSnapshot(
|
||||
_serviceClient, isolateRef);
|
||||
_serviceClient, _isolateRef);
|
||||
// TODO: Considering the single source shortest path algorithm
|
||||
// doesn't seem to give a useful trace anymore (the snapshot seems to
|
||||
// have changed) and that converting the graph is very costly,
|
||||
// maybe don't do that...
|
||||
HeapGraph graph = convertHeapGraph(heapSnapshotGraph);
|
||||
|
||||
Set<String> seenPrints = {};
|
||||
|
@ -363,48 +409,54 @@ class VMServiceHeapHelperSpecificExactLeakFinder
|
|||
for (String s in duplicatePrints) {
|
||||
int count = groupedByToString[s].length;
|
||||
print("$s ($count)");
|
||||
for (HeapGraphElement duplicate in groupedByToString[s]) {
|
||||
print(" => ${duplicate.getPrettyPrint(_prettyPrints)}");
|
||||
}
|
||||
print("");
|
||||
}
|
||||
print("======================================");
|
||||
for (String duplicateString in duplicatePrints) {
|
||||
print("$duplicateString:");
|
||||
List<HeapGraphElement> Function(HeapGraphElement target)
|
||||
dijkstraTarget = dijkstra(graph.elements.first, graph);
|
||||
for (HeapGraphElement duplicate
|
||||
in groupedByToString[duplicateString]) {
|
||||
print("${duplicate} pointed to from:");
|
||||
print(duplicate.getPrettyPrint(_prettyPrints));
|
||||
List<HeapGraphElement> shortestPath = dijkstraTarget(duplicate);
|
||||
for (int i = 0; i < shortestPath.length - 1; i++) {
|
||||
HeapGraphElement thisOne = shortestPath[i];
|
||||
HeapGraphElement nextOne = shortestPath[i + 1];
|
||||
String indexFieldName;
|
||||
if (thisOne is HeapGraphElementActual) {
|
||||
HeapGraphClass c = thisOne.class_;
|
||||
if (c is HeapGraphClassActual) {
|
||||
for (vmService.HeapSnapshotField field in c.origin.fields) {
|
||||
if (thisOne.references[field.index] == nextOne) {
|
||||
indexFieldName = field.name;
|
||||
if (tryToFindShortestPathToLeaks) {
|
||||
print("======================================");
|
||||
for (String duplicateString in duplicatePrints) {
|
||||
print("$duplicateString:");
|
||||
List<HeapGraphElement> Function(HeapGraphElement target)
|
||||
dijkstraTarget = dijkstra(graph.elements.first, graph);
|
||||
for (HeapGraphElement duplicate
|
||||
in groupedByToString[duplicateString]) {
|
||||
print("${duplicate} pointed to from:");
|
||||
print(duplicate.getPrettyPrint(_prettyPrints));
|
||||
List<HeapGraphElement> shortestPath = dijkstraTarget(duplicate);
|
||||
for (int i = 0; i < shortestPath.length - 1; i++) {
|
||||
HeapGraphElement thisOne = shortestPath[i];
|
||||
HeapGraphElement nextOne = shortestPath[i + 1];
|
||||
String indexFieldName;
|
||||
if (thisOne is HeapGraphElementActual) {
|
||||
HeapGraphClass c = thisOne.class_;
|
||||
if (c is HeapGraphClassActual) {
|
||||
for (vmService.HeapSnapshotField field in c.origin.fields) {
|
||||
if (thisOne.references[field.index] == nextOne) {
|
||||
indexFieldName = field.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (indexFieldName == null) {
|
||||
indexFieldName = "no field found; index "
|
||||
"${thisOne.references.indexOf(nextOne)}";
|
||||
}
|
||||
print(" $thisOne -> $nextOne ($indexFieldName)");
|
||||
}
|
||||
if (indexFieldName == null) {
|
||||
indexFieldName = "no field found; index "
|
||||
"${thisOne.references.indexOf(nextOne)}";
|
||||
}
|
||||
print(" $thisOne -> $nextOne ($indexFieldName)");
|
||||
print("---------------------------");
|
||||
}
|
||||
print("---------------------------");
|
||||
}
|
||||
}
|
||||
|
||||
if (throwOnPossibleLeak) {
|
||||
debugger();
|
||||
throw "Possible leak detected.";
|
||||
}
|
||||
}
|
||||
await _serviceClient.resume(isolateRef.id);
|
||||
|
||||
await _serviceClient.resume(_isolateRef.id);
|
||||
_iterationNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -545,7 +597,6 @@ HeapGraph convertHeapGraph(vmService.HeapSnapshotGraph graph) {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new HeapGraph(classSentinel, classes, elementSentinel, elements);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
set -e
|
||||
|
||||
prepareOnly=false
|
||||
leakTest=false
|
||||
|
||||
REMAINING_ARGS=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
|
@ -16,6 +17,10 @@ while [[ $# -gt 0 ]]; do
|
|||
prepareOnly=true
|
||||
shift
|
||||
;;
|
||||
--leakTest|--leak-test|--leak_test)
|
||||
leakTest=true
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
REMAINING_ARGS+=("$1")
|
||||
shift
|
||||
|
@ -27,6 +32,8 @@ set -- "${REMAINING_ARGS[@]}"
|
|||
|
||||
if $prepareOnly; then
|
||||
echo "Will prepare only!"
|
||||
elif $leakTest; then
|
||||
echo "Will run leak test"
|
||||
fi
|
||||
|
||||
checkout=$(pwd)
|
||||
|
@ -106,6 +113,11 @@ if $prepareOnly; then
|
|||
echo "Preparations complete!"
|
||||
echo "Flutter is now in $tmpdir/flutter and the patched sdk in $tmpdir/flutter_patched_sdk"
|
||||
echo "You can run the test with $dart --enable-asserts pkg/frontend_server/test/frontend_server_flutter.dart --flutterDir=$tmpdir/flutter --flutterPlatformDir=$tmpdir/flutter_patched_sdk"
|
||||
elif $leakTest; then
|
||||
$dart \
|
||||
--enable-asserts \
|
||||
pkg/front_end/test/flutter_gallery_leak_tester.dart \
|
||||
--path=$tmpdir
|
||||
else
|
||||
$dart \
|
||||
--enable-asserts \
|
||||
|
|
|
@ -3437,6 +3437,32 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"builders": [
|
||||
"frontend-weekly"
|
||||
],
|
||||
"meta": {
|
||||
"description": "This configuration is used for running slow frontend tests for instance weekly."
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"name": "build dart",
|
||||
"script": "tools/build.py",
|
||||
"arguments": [
|
||||
"--mode=release",
|
||||
"--arch=x64",
|
||||
"create_sdk"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "run tests",
|
||||
"script": "tools/bots/flutter/compile_flutter.sh",
|
||||
"arguments": [
|
||||
"--leakTest"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"builders": [
|
||||
"fuzz-linux"
|
||||
|
|
Loading…
Reference in a new issue