Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(432)

Unified Diff: pkg/analysis_server/test/stress/replay/replay.dart

Issue 1471443002: Initial work toward a stress test (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: pkg/analysis_server/test/stress/replay/replay.dart
diff --git a/pkg/analysis_server/test/stress/replay/replay.dart b/pkg/analysis_server/test/stress/replay/replay.dart
new file mode 100644
index 0000000000000000000000000000000000000000..37de9114da57fad6d84c2a367d62ad81879a35b2
--- /dev/null
+++ b/pkg/analysis_server/test/stress/replay/replay.dart
@@ -0,0 +1,467 @@
+// Copyright (c) 2015, 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.
+
+/**
+ * A stress test for the analysis server.
+ */
+library analysis_server.test.stress.replay.replay;
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:analysis_server/plugin/protocol/protocol.dart';
+import 'package:analyzer/src/generated/java_engine.dart';
+import 'package:analyzer/src/generated/source.dart';
+import 'package:analyzer/src/util/glob.dart';
+import 'package:args/args.dart';
+import 'package:path/path.dart' as path;
+
+import '../utilities/git.dart';
+import '../utilities/server.dart';
+import 'operation.dart';
+
+/**
+ * Run the simulation based on the given command-line [arguments].
+ */
+Future main(List<String> arguments) async {
+ Driver driver = new Driver();
+ await driver.run(arguments);
+}
+
+/**
+ * The driver class that runs the simulation.
+ */
+class Driver {
+ /**
+ * The name of the command-line flag that will print help text.
+ */
+ static String HELP_FLAG_NAME = 'help';
+
+ /**
+ * The name of the pubspec file.
+ */
+ static const String PUBSPEC_FILE_NAME = 'pubspec.yaml';
+
+ /**
+ * The name of the branch used to clean-up after making temporary changes.
+ */
+ static const String TEMP_BRANCH_NAME = 'temp';
+
+ /**
+ * The absolute path of the repository.
+ */
+ String repositoryPath;
+
+ /**
+ * The absolute paths to the analysis roots.
+ */
+ List<String> analysisRoots;
+
+ /**
+ * The git repository.
+ */
+ GitRepository repository;
+
+ /**
+ * The connection to the analysis server.
+ */
+ Server server = new Server();
+
+ /**
+ * A list of the glob patterns used to identify the files being analyzed by
+ * the server.
+ */
+ List<Glob> fileGlobs;
+
+ /**
+ * An object gathering statistics about the simulation.
+ */
+ Statistics statistics;
+
+ /**
+ * Initialize a newly created driver.
+ */
+ Driver() {
+ statistics = new Statistics(this);
+ }
+
+ /**
+ * Run the test based on the given command-line arguments ([args]).
+ */
+ Future run(List<String> args) async {
+ //
+ // Process the command-line arguments.
+ //
+ ArgParser parser = _createArgParser();
+ ArgResults results;
+ try {
+ results = parser.parse(args);
+ } catch (exception) {
+ _showUsage(parser);
+ return null;
+ }
+
+ if (results[HELP_FLAG_NAME]) {
+ _showUsage(parser);
+ return null;
+ }
+
+ List<String> arguments = results.arguments;
+ if (arguments.length < 2) {
+ _showUsage(parser);
+ return null;
+ }
+ repositoryPath = path.normalize(arguments[0]);
+ repository = new GitRepository(repositoryPath);
+
+ analysisRoots = arguments
+ .sublist(1)
+ .map((String analysisRoot) => path.normalize(analysisRoot))
+ .toList();
+ for (String analysisRoot in analysisRoots) {
+ if (repositoryPath != analysisRoot &&
+ !path.isWithin(repositoryPath, analysisRoot)) {
+ _showUsage(parser,
+ 'Analysis roots must be contained within the repository: $analysisRoot');
+ return null;
+ }
+ }
+ //
+ // Replay the commit history.
+ //
+ Stopwatch stopwatch = new Stopwatch();
+ statistics.stopwatch = stopwatch;
+ stopwatch.start();
+ await server.start();
+ server.sendServerSetSubscriptions([ServerService.STATUS]);
+ server.sendAnalysisSetGeneralSubscriptions(
+ [GeneralAnalysisService.ANALYZED_FILES]);
+ // TODO(brianwilkerson) Get the list of glob patterns from the server after
+ // an API for getting them has been implemented.
+ fileGlobs = <Glob>[
+ new Glob(path.context.separator, '**.dart'),
+ new Glob(path.context.separator, '**.html'),
+ new Glob(path.context.separator, '**.htm'),
+ new Glob(path.context.separator, '**/.analysisOptions')
+ ];
+ try {
+ _replayChanges();
+ } finally {
+ server.sendServerShutdown();
+ repository.checkout('master');
+ }
+ stopwatch.stop();
+ //
+ // Print out statistics gathered while performing the simulation.
+ //
+ statistics.print();
+ return null;
+ }
+
+ /**
+ * Create and return a parser that can be used to parse the command-line
+ * arguments.
+ */
+ ArgParser _createArgParser() {
+ ArgParser parser = new ArgParser();
+ parser.addFlag(HELP_FLAG_NAME,
+ abbr: 'h',
+ help: 'Print usage information',
+ defaultsTo: false,
+ negatable: false);
+ return parser;
+ }
+
+ void _createSourceEdits(FileEdit fileEdit, BlobDiff blobDiff) {
+ LineInfo info = fileEdit.lineInfo;
+ for (DiffHunk hunk in blobDiff.hunks) {
+ List<SourceEdit> sourceEdits = <SourceEdit>[];
+ int srcStart = info.getOffsetOfLine(hunk.srcLine);
+ int srcEnd = info.getOffsetOfLine(hunk.srcLine + hunk.removeLines.length);
+ // TODO(brianwilkerson) Create multiple edits instead of a single edit.
+ sourceEdits.add(new SourceEdit(
+ srcStart, srcEnd - srcStart + 1, _join(hunk.addLines)));
+ fileEdit.addSourceEdits(sourceEdits);
+ }
+ }
+
+ /**
+ * Return athe absolute paths of all of the pubspec files in all of the
+ * analysis roots.
+ */
+ Iterable<String> _findPubspecsInAnalysisRoots() {
+ List<String> pubspecFiles = <String>[];
+ for (String directoryPath in analysisRoots) {
+ Directory directory = new Directory(directoryPath);
+ List<FileSystemEntity> children =
+ directory.listSync(recursive: true, followLinks: false);
+ for (FileSystemEntity child in children) {
+ String filePath = child.path;
+ if (path.basename(filePath) == PUBSPEC_FILE_NAME) {
+ pubspecFiles.add(filePath);
+ }
+ }
+ }
+ return pubspecFiles;
+ }
+
+ String _join(List<String> lines) {
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < lines.length; i++) {
+ buffer.writeln(lines[i]);
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Replay the changes in each commit.
+ */
+ void _replayChanges() {
+ //
+ // Get the revision history of the repo.
+ //
+ LinearCommitHistory history = repository.getCommitHistory();
+ statistics.commitCount = history.commitIds.length;
+ LinearCommitHistoryIterator iterator = history.iterator();
+ //
+ // Iterate over the history, applying changes.
+ //
+ bool firstCheckout = true;
+// Map<String, List<AnalysisError>> expectedErrors = null;
+ Iterable<String> changedPubspecs;
+ while (iterator.moveNext()) {
+ //
+ // Checkout the commit on which the changes are based.
+ //
+ repository.checkout(iterator.srcCommit);
+// if (expectedErrors != null) {
+// await server.analysisFinished;
+// server.expectErrorState(expectedErrors);
+// }
+ if (firstCheckout) {
+ changedPubspecs = _findPubspecsInAnalysisRoots();
+ server.sendAnalysisSetAnalysisRoots(analysisRoots, []);
+ firstCheckout = false;
+ } else {
+ server.removeAllOverlays();
+ }
+// await server.analysisFinished;
+// expectedErrors = server.errorMap;
+ for (String filePath in changedPubspecs) {
+ _runPub(filePath);
+ }
+ //
+ // Apply the changes.
+ //
+ CommitDelta commitDelta = iterator.next();
+ commitDelta.filterDiffs(analysisRoots, fileGlobs);
+ if (commitDelta.hasDiffs) {
+ statistics.commitsWithChangeInRootCount++;
+ _replayDiff(commitDelta);
+ }
+ changedPubspecs = commitDelta.filesMatching(PUBSPEC_FILE_NAME);
+ }
+ server.removeAllOverlays();
+ }
+
+ void _replayDiff(CommitDelta commitDelta) {
+ List<FileEdit> editList = <FileEdit>[];
+ for (DiffRecord record in commitDelta.diffRecords) {
+ FileEdit edit = new FileEdit(record);
+ _createSourceEdits(edit, record.getBlobDiff());
+ editList.add(edit);
+ }
+ // TODO(brianwilkerson) Randomize.
+ // Randomly select operations from different files to simulate a user
+ // editing multiple files simultaneously.
+ for (FileEdit edit in editList) {
+ List<String> currentFile = <String>[edit.filePath];
+ server.sendAnalysisSetPriorityFiles(currentFile);
+ server.sendAnalysisSetSubscriptions({
+ AnalysisService.FOLDING: currentFile,
+ AnalysisService.HIGHLIGHTS: currentFile,
+ AnalysisService.IMPLEMENTED: currentFile,
+ AnalysisService.NAVIGATION: currentFile,
+ AnalysisService.OCCURRENCES: currentFile,
+ AnalysisService.OUTLINE: currentFile,
+ AnalysisService.OVERRIDES: currentFile
+ });
+ for (ServerOperation operation in edit.getOperations()) {
+ operation.perform(server);
+ }
+ }
+ }
+
+ /**
+ * Run `pub` on the pubspec with the given [filePath].
+ */
+ void _runPub(String filePath) {
+ String directoryPath = path.dirname(filePath);
+ if (new Directory(directoryPath).existsSync()) {
+ Process.runSync(
+ '/Users/brianwilkerson/Dev/dart/dart-sdk/bin/pub', ['get'],
+ workingDirectory: directoryPath);
+ }
+ }
+
+ /**
+ * Display usage information, preceeded by the [errorMessage] if one is given.
+ */
+ void _showUsage(ArgParser parser, [String errorMessage = null]) {
+ if (errorMessage != null) {
+ stderr.writeln(errorMessage);
+ stderr.writeln();
+ }
+ stderr.writeln('''
+Usage: replay [options...] repositoryPath analysisRoot...
+
+Uses the commit history of the git repository at the given repository path to
+simulate the development of a code base while using the analysis server to
+analyze the code base.
+
+The repository path must be the absolute path of a directory containing a git
+repository.
+
+There must be at least one analysis root, and all of the analysis roots must be
+the absolute path of a directory contained within the repository directory. The
+analysis roots represent the portion of the repository that will be analyzed by
+the analysis server.
+
+OPTIONS:''');
+ stderr.writeln(parser.usage);
+ }
+}
+
+/**
+ * A representation of the edits to be applied to a single file.
+ */
+class FileEdit {
+ /**
+ * The absolute path of the file to be edited.
+ */
+ String filePath;
+
+ /**
+ * The content of the file before any edits have been applied.
+ */
+ String content;
+
+ /**
+ * The line info for the file before any edits have been applied.
+ */
+ LineInfo lineInfo;
+
+ /**
+ * The lists of source edits, one list for each hunk being edited.
+ */
+ List<List<SourceEdit>> editLists = <List<SourceEdit>>[];
+
+ /**
+ * Initialize a collection of edits to be associated with the file at the
+ * given [filePath].
+ */
+ FileEdit(DiffRecord record) {
+ filePath = record.srcPath;
+ if (record.isAddition) {
+ content = '';
+ lineInfo = new LineInfo(<int>[0]);
+ } else if (record.isCopy || record.isRename || record.isTypeChange) {
+ throw new ArgumentError('Unhandled change of type ${record.status}');
+ } else {
+ content = new File(filePath).readAsStringSync();
+ lineInfo = new LineInfo(StringUtilities.computeLineStarts(content));
+ }
+ }
+
+ /**
+ * Add a list of source edits that, taken together, transform a single hunk in
+ * the file.
+ */
+ void addSourceEdits(List<SourceEdit> sourceEdits) {
+ editLists.add(sourceEdits);
+ }
+
+ /**
+ * Return a list of operations to be sent to the server.
+ */
+ List<ServerOperation> getOperations() {
+ // TODO(brianwilkerson) Randomize.
+ // Make the order of edits random. Doing so will require updating the
+ // offsets of edits after the selected edit point.
+ List<ServerOperation> operations = <ServerOperation>[];
+ operations.add(
+ new AnalysisUpdateContent(filePath, new AddContentOverlay(content)));
+ for (List<SourceEdit> editList in editLists.reversed) {
+ for (SourceEdit edit in editList.reversed) {
+ operations.add(new AnalysisUpdateContent(
+ filePath, new ChangeContentOverlay([edit])));
+ }
+ }
+ operations
+ .add(new AnalysisUpdateContent(filePath, new RemoveContentOverlay()));
+ return operations;
+ }
+}
+
+/**
+ * A set of statistics related to the execution of the simulation.
+ */
+class Statistics {
+ /**
+ * The driver driving the simulation.
+ */
+ final Driver driver;
+
+ /**
+ * The stopwatch being used to time the simulation.
+ */
+ Stopwatch stopwatch;
+
+ /**
+ * The total number of commits in the repository.
+ */
+ int commitCount;
+
+ /**
+ * The number of commits in the repository that touched one of the files in
+ * one of the analysis roots.
+ */
+ int commitsWithChangeInRootCount = 0;
+
+ /**
+ * Initialize a newly created set of statistics.
+ */
+ Statistics(this.driver);
+
+ void print() {
+ stdout.write('Replay commits in ');
+ stdout.writeln(driver.repositoryPath);
+ stdout.write(' replay took ');
+ stdout.writeln(_printTime(stopwatch.elapsedMilliseconds));
+ stdout.write(' analysis roots = ');
+ stdout.writeln(driver.analysisRoots);
+ stdout.write(' number of commits = ');
+ stdout.writeln(commitCount);
+ stdout.write(' number of commits with a change in an analysis root = ');
+ stdout.writeln(commitsWithChangeInRootCount);
+ }
+
+ String _printTime(int milliseconds) {
+ int seconds = milliseconds ~/ 1000;
+ milliseconds -= seconds * 1000;
+ int minutes = seconds ~/ 60;
+ seconds -= minutes * 60;
+ int hours = minutes ~/ 60;
+ minutes -= hours * 60;
+
+ if (hours > 0) {
+ return '$hours:$minutes:$seconds.$milliseconds';
+ } else if (minutes > 0) {
+ return '$minutes:$seconds.$milliseconds m';
+ } else if (seconds > 0) {
+ return '$seconds.$milliseconds s';
+ }
+ return '$milliseconds ms';
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698