blob: a48773a336977d3cba321363f7df5cc5776b2e08 [file] [log] [blame]
// Copyright (c) 2025, 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:io';
import '../language_server_benchmark.dart';
import '../utils.dart';
RunDetails copyData(
Uri packageDirUri,
Uri outerDirForAdditionalData,
int numFiles,
CodeType copyType,
List<String> args, {
required bool includePlugin,
}) {
Uri filesUri = Platform.script.resolve('files/');
Uri libDirUri = packageDirUri.resolve('lib/');
Directory.fromUri(libDirUri).createSync();
Directory files = Directory.fromUri(filesUri);
for (var file in files.listSync()) {
if (file is! File) continue;
String filename = file.uri.pathSegments.last;
if (filename == 'analysis_options.yaml') {
// Written below at the right place instead.
continue;
}
file.copySync(libDirUri.resolve(filename).toFilePath());
}
// Write analysis_options.
File.fromUri(
packageDirUri.resolve('analysis_options.yaml'),
).writeAsStringSync('''
analyzer:
errors:
todo: ignore
''');
File copyMe = File.fromUri(filesUri.resolve('copy_me/copy_me.dart'));
String copyMeData = copyMe.readAsStringSync();
Uri copyToDir = libDirUri.resolve('copies/');
Directory.fromUri(copyToDir).createSync(recursive: true);
List<FileContentPair> orderedFileCopies = [];
for (int i = 1; i <= numFiles; i++) {
String nextFile = getFilenameFor(i == numFiles ? 1 : i + 1);
String import = "import '$nextFile' as nextFile;";
String export = "export '$nextFile';";
switch (copyType) {
case CodeType.ImportCycle:
export = '';
case CodeType.ImportChain:
export = '';
if (i == numFiles) {
import = '';
}
case CodeType.ImportExportChain:
if (i == numFiles) {
import = '';
export = '';
}
case CodeType.ImportCycleExportChain:
if (i == numFiles) {
export = '';
}
case CodeType.ImportExportCycle:
// As default values.
}
String fooMethod;
if (import.isEmpty) {
fooMethod = '''
String foo(int i) {
if (i == 0) return "foo";
return "bar";
}''';
} else {
fooMethod = '''
String foo(int i) {
if (i == 0) return "foo";
return nextFile.foo(i-1);
}''';
}
var content = '''
$import
$export
$copyMeData
$fooMethod
String get$i() {
return "$i";
}
''';
var copyUri = copyToDir.resolve(getFilenameFor(i));
File.fromUri(copyUri).writeAsStringSync(content);
orderedFileCopies.add(FileContentPair(copyUri, content));
}
var mainFileUri = copyToDir.resolve('main.dart');
var mainFileContent = """
import '${getFilenameFor(1)}';
void main(List<String> arguments) {
}
""";
File.fromUri(mainFileUri).writeAsStringSync(mainFileContent);
var typing = ' ge';
var mainFileTypingContent = """
import '${getFilenameFor(1)}';
void main(List<String> arguments) {
$typing
}
""";
var typingAtOffset = mainFileTypingContent.indexOf(typing) + typing.length;
if (includePlugin) {
String executableToUse = extractDartParamOrDefault(args);
void pubGetIn(String dir) {
if (Process.runSync(executableToUse, [
'pub',
'get',
], workingDirectory: dir).exitCode !=
0) {
print('WARNING: Got non 0 exit-code from dart pub get call.');
}
}
// Plugin package pubspec.yaml
var pluginDir = Directory.fromUri(
outerDirForAdditionalData.resolve('plugin/'),
)..createSync(recursive: true);
File.fromUri(pluginDir.uri.resolve('pubspec.yaml')).writeAsStringSync('''
name: benchmark_helper_plugin
environment:
sdk: '>=3.3.0 <5.0.0'
''');
pubGetIn(pluginDir.path);
// Plugin package lib/<package_name>.dart
var pluginLibDir = Directory.fromUri(
outerDirForAdditionalData.resolve('plugin/lib/'),
)..createSync(recursive: true);
File.fromUri(
pluginLibDir.uri.resolve('benchmark_helper_plugin.dart'),
).writeAsStringSync('');
// tools/analyzer_plugin/bin/plugin.dart
var dir = Directory.fromUri(
pluginDir.uri.resolve('tools/analyzer_plugin/bin/'),
)..createSync(recursive: true);
File copyMe = File.fromUri(filesUri.resolve('copy_me/plugin.dart'));
copyMe.copySync(dir.uri.resolve('plugin.dart').toFilePath());
// tools/analyzer_plugin/pubspec.yaml
dir = Directory.fromUri(pluginDir.uri.resolve('tools/analyzer_plugin/'));
File.fromUri(dir.uri.resolve('pubspec.yaml')).writeAsStringSync('''
name: benchmark_helper_plugin_helper
environment:
sdk: '>=3.3.0 <5.0.0'
''');
pubGetIn(dir.path);
// pubspec.yaml in package that is analyzed, depending on the plugin
File.fromUri(packageDirUri.resolve('pubspec.yaml')).writeAsStringSync('''
name: benchmark_project
environment:
sdk: '>=3.3.0 <5.0.0'
dependencies:
benchmark_helper_plugin:
path: ${pluginDir.path}
''');
pubGetIn(packageDirUri.path);
// Write analysis_options enabling the plugin.
File.fromUri(
packageDirUri.resolve('analysis_options.yaml'),
).writeAsStringSync('''
analyzer:
errors:
todo: ignore
plugins:
- benchmark_helper_plugin
''');
}
return RunDetails(
libDirUri: libDirUri,
mainFile: FileContentPair(mainFileUri, mainFileContent),
mainFileTypingContent: mainFileTypingContent,
typingAtOffset: typingAtOffset,
orderedFileCopies: orderedFileCopies,
numFiles: numFiles,
);
}
String formatDuration(Duration duration) {
int seconds = duration.inSeconds;
int ms = duration.inMicroseconds - seconds * Duration.microsecondsPerSecond;
return '$seconds.${ms.toString().padLeft(6, '0')}';
}
String formatKb(int kb) {
if (kb > 1024) {
return '${kb ~/ 1024} MB';
} else {
return '$kb KB';
}
}
String getFilenameFor(int i) {
return "file${i.toString().padLeft(5, '0')}.dart";
}
Future<void> runHelper(
List<String> args,
DartLanguageServerBenchmark Function(
List<String> args,
Uri rootUri,
Uri cacheFolder,
RunDetails runDetails,
)
benchmarkCreator, {
required bool runAsLsp,
List<int> numberOfFileOptions = const [16, 32, 64, 128, 256, 512, 1024],
List<CodeType> codeTypes = CodeType.values,
bool includePlugin = false,
}) async {
int verbosity = 0;
bool jsonOutput = false;
for (String arg in args) {
if (arg.startsWith('--files=')) {
numberOfFileOptions =
arg.substring('--files='.length).split(',').map(int.parse).toList();
} else if (arg.startsWith('--types=')) {
codeTypes = [];
for (var type in arg.substring('--types='.length).split(',')) {
type = type.toLowerCase();
for (var value in CodeType.values) {
if (value.name.toLowerCase().contains(type)) {
codeTypes.add(value);
}
}
}
} else if (arg.startsWith('--verbosity=')) {
verbosity = int.parse(arg.substring('--verbosity='.length));
} else if (arg == '--json') {
jsonOutput = true;
}
}
if (jsonOutput) {
verbosity = -1;
}
StringBuffer sb = StringBuffer();
Map<String, int> jsonData = {};
for (CodeType codeType in codeTypes) {
for (int numFiles in numberOfFileOptions) {
try {
Directory tmpDir = Directory.systemTemp.createTempSync('lsp_benchmark');
try {
Directory cacheDir = Directory.fromUri(tmpDir.uri.resolve('cache/'))
..createSync(recursive: true);
Directory dartDir = Directory.fromUri(tmpDir.uri.resolve('dart/'))
..createSync(recursive: true);
var runDetails = copyData(
dartDir.uri,
tmpDir.uri,
numFiles,
codeType,
args,
includePlugin: includePlugin,
);
var benchmark = benchmarkCreator(
args,
dartDir.uri,
cacheDir.uri,
runDetails,
);
try {
benchmark.verbosity = verbosity;
await benchmark.run();
} finally {
benchmark.exit();
}
var caption = '$numFiles files / $codeType';
if (jsonOutput) {
for (var durationInfo in benchmark.durationInfo) {
var key = '$caption: ${durationInfo.name} (ms)';
if (jsonData.containsKey(key)) {
throw 'Already contains data for $key';
}
jsonData[key] = durationInfo.duration.inMilliseconds;
}
for (var memoryInfo in benchmark.memoryInfo) {
jsonData['$caption: ${memoryInfo.name} (kb)'] = memoryInfo.kb;
}
} else {
if (verbosity >= 0) print('====================');
if (verbosity >= 0) print('$caption:');
sb.writeln('$caption:');
for (var durationInfo in benchmark.durationInfo) {
if (verbosity >= 0) {
print(
'${durationInfo.name}: '
'${formatDuration(durationInfo.duration)}',
);
}
sb.writeln(
'${durationInfo.name}: '
'${formatDuration(durationInfo.duration)}',
);
}
for (var memoryInfo in benchmark.memoryInfo) {
if (verbosity >= 0) {
print(
'${memoryInfo.name}: '
'${formatKb(memoryInfo.kb)}',
);
}
sb.writeln(
'${memoryInfo.name}: '
'${formatKb(memoryInfo.kb)}',
);
}
if (verbosity >= 0) print('====================');
sb.writeln();
}
} finally {
try {
tmpDir.deleteSync(recursive: true);
} catch (e) {
// Wait a little and retry.
sleep(const Duration(milliseconds: 42));
try {
tmpDir.deleteSync(recursive: true);
} catch (e) {
if (verbosity >= 0) print('Warning: $e');
}
}
}
} catch (e) {
stderr.writeln(
'Error while processing $numFiles files / $codeType: $e',
);
}
}
}
if (jsonOutput) {
print(json.encode(jsonData));
} else {
print('==================================');
print(sb.toString().trim());
print('==================================');
}
}
enum CodeType {
ImportCycle,
ImportChain,
ImportExportCycle,
ImportExportChain,
ImportCycleExportChain,
}
class FileContentPair {
final Uri uri;
final String content;
FileContentPair(this.uri, this.content);
}
class RunDetails {
final Uri libDirUri;
final FileContentPair mainFile;
final String mainFileTypingContent;
final int typingAtOffset;
final List<FileContentPair> orderedFileCopies;
final int numFiles;
RunDetails({
required this.libDirUri,
required this.mainFile,
required this.mainFileTypingContent,
required this.typingAtOffset,
required this.orderedFileCopies,
required this.numFiles,
});
}