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

Side by Side 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 /**
6 * A stress test for the analysis server.
7 */
8 library analysis_server.test.stress.replay.replay;
9
10 import 'dart:async';
11 import 'dart:io';
12
13 import 'package:analysis_server/plugin/protocol/protocol.dart';
14 import 'package:analyzer/src/generated/java_engine.dart';
15 import 'package:analyzer/src/generated/source.dart';
16 import 'package:analyzer/src/util/glob.dart';
17 import 'package:args/args.dart';
18 import 'package:path/path.dart' as path;
19
20 import '../utilities/git.dart';
21 import '../utilities/server.dart';
22 import 'operation.dart';
23
24 /**
25 * Run the simulation based on the given command-line [arguments].
26 */
27 Future main(List<String> arguments) async {
28 Driver driver = new Driver();
29 await driver.run(arguments);
30 }
31
32 /**
33 * The driver class that runs the simulation.
34 */
35 class Driver {
36 /**
37 * The name of the command-line flag that will print help text.
38 */
39 static String HELP_FLAG_NAME = 'help';
40
41 /**
42 * The name of the pubspec file.
43 */
44 static const String PUBSPEC_FILE_NAME = 'pubspec.yaml';
45
46 /**
47 * The name of the branch used to clean-up after making temporary changes.
48 */
49 static const String TEMP_BRANCH_NAME = 'temp';
50
51 /**
52 * The absolute path of the repository.
53 */
54 String repositoryPath;
55
56 /**
57 * The absolute paths to the analysis roots.
58 */
59 List<String> analysisRoots;
60
61 /**
62 * The git repository.
63 */
64 GitRepository repository;
65
66 /**
67 * The connection to the analysis server.
68 */
69 Server server = new Server();
70
71 /**
72 * A list of the glob patterns used to identify the files being analyzed by
73 * the server.
74 */
75 List<Glob> fileGlobs;
76
77 /**
78 * An object gathering statistics about the simulation.
79 */
80 Statistics statistics;
81
82 /**
83 * Initialize a newly created driver.
84 */
85 Driver() {
86 statistics = new Statistics(this);
87 }
88
89 /**
90 * Run the test based on the given command-line arguments ([args]).
91 */
92 Future run(List<String> args) async {
93 //
94 // Process the command-line arguments.
95 //
96 ArgParser parser = _createArgParser();
97 ArgResults results;
98 try {
99 results = parser.parse(args);
100 } catch (exception) {
101 _showUsage(parser);
102 return null;
103 }
104
105 if (results[HELP_FLAG_NAME]) {
106 _showUsage(parser);
107 return null;
108 }
109
110 List<String> arguments = results.arguments;
111 if (arguments.length < 2) {
112 _showUsage(parser);
113 return null;
114 }
115 repositoryPath = path.normalize(arguments[0]);
116 repository = new GitRepository(repositoryPath);
117
118 analysisRoots = arguments
119 .sublist(1)
120 .map((String analysisRoot) => path.normalize(analysisRoot))
121 .toList();
122 for (String analysisRoot in analysisRoots) {
123 if (repositoryPath != analysisRoot &&
124 !path.isWithin(repositoryPath, analysisRoot)) {
125 _showUsage(parser,
126 'Analysis roots must be contained within the repository: $analysisRo ot');
127 return null;
128 }
129 }
130 //
131 // Replay the commit history.
132 //
133 Stopwatch stopwatch = new Stopwatch();
134 statistics.stopwatch = stopwatch;
135 stopwatch.start();
136 await server.start();
137 server.sendServerSetSubscriptions([ServerService.STATUS]);
138 server.sendAnalysisSetGeneralSubscriptions(
139 [GeneralAnalysisService.ANALYZED_FILES]);
140 // TODO(brianwilkerson) Get the list of glob patterns from the server after
141 // an API for getting them has been implemented.
142 fileGlobs = <Glob>[
143 new Glob(path.context.separator, '**.dart'),
144 new Glob(path.context.separator, '**.html'),
145 new Glob(path.context.separator, '**.htm'),
146 new Glob(path.context.separator, '**/.analysisOptions')
147 ];
148 try {
149 _replayChanges();
150 } finally {
151 server.sendServerShutdown();
152 repository.checkout('master');
153 }
154 stopwatch.stop();
155 //
156 // Print out statistics gathered while performing the simulation.
157 //
158 statistics.print();
159 return null;
160 }
161
162 /**
163 * Create and return a parser that can be used to parse the command-line
164 * arguments.
165 */
166 ArgParser _createArgParser() {
167 ArgParser parser = new ArgParser();
168 parser.addFlag(HELP_FLAG_NAME,
169 abbr: 'h',
170 help: 'Print usage information',
171 defaultsTo: false,
172 negatable: false);
173 return parser;
174 }
175
176 void _createSourceEdits(FileEdit fileEdit, BlobDiff blobDiff) {
177 LineInfo info = fileEdit.lineInfo;
178 for (DiffHunk hunk in blobDiff.hunks) {
179 List<SourceEdit> sourceEdits = <SourceEdit>[];
180 int srcStart = info.getOffsetOfLine(hunk.srcLine);
181 int srcEnd = info.getOffsetOfLine(hunk.srcLine + hunk.removeLines.length);
182 // TODO(brianwilkerson) Create multiple edits instead of a single edit.
183 sourceEdits.add(new SourceEdit(
184 srcStart, srcEnd - srcStart + 1, _join(hunk.addLines)));
185 fileEdit.addSourceEdits(sourceEdits);
186 }
187 }
188
189 /**
190 * Return athe absolute paths of all of the pubspec files in all of the
191 * analysis roots.
192 */
193 Iterable<String> _findPubspecsInAnalysisRoots() {
194 List<String> pubspecFiles = <String>[];
195 for (String directoryPath in analysisRoots) {
196 Directory directory = new Directory(directoryPath);
197 List<FileSystemEntity> children =
198 directory.listSync(recursive: true, followLinks: false);
199 for (FileSystemEntity child in children) {
200 String filePath = child.path;
201 if (path.basename(filePath) == PUBSPEC_FILE_NAME) {
202 pubspecFiles.add(filePath);
203 }
204 }
205 }
206 return pubspecFiles;
207 }
208
209 String _join(List<String> lines) {
210 StringBuffer buffer = new StringBuffer();
211 for (int i = 0; i < lines.length; i++) {
212 buffer.writeln(lines[i]);
213 }
214 return buffer.toString();
215 }
216
217 /**
218 * Replay the changes in each commit.
219 */
220 void _replayChanges() {
221 //
222 // Get the revision history of the repo.
223 //
224 LinearCommitHistory history = repository.getCommitHistory();
225 statistics.commitCount = history.commitIds.length;
226 LinearCommitHistoryIterator iterator = history.iterator();
227 //
228 // Iterate over the history, applying changes.
229 //
230 bool firstCheckout = true;
231 // Map<String, List<AnalysisError>> expectedErrors = null;
232 Iterable<String> changedPubspecs;
233 while (iterator.moveNext()) {
234 //
235 // Checkout the commit on which the changes are based.
236 //
237 repository.checkout(iterator.srcCommit);
238 // if (expectedErrors != null) {
239 // await server.analysisFinished;
240 // server.expectErrorState(expectedErrors);
241 // }
242 if (firstCheckout) {
243 changedPubspecs = _findPubspecsInAnalysisRoots();
244 server.sendAnalysisSetAnalysisRoots(analysisRoots, []);
245 firstCheckout = false;
246 } else {
247 server.removeAllOverlays();
248 }
249 // await server.analysisFinished;
250 // expectedErrors = server.errorMap;
251 for (String filePath in changedPubspecs) {
252 _runPub(filePath);
253 }
254 //
255 // Apply the changes.
256 //
257 CommitDelta commitDelta = iterator.next();
258 commitDelta.filterDiffs(analysisRoots, fileGlobs);
259 if (commitDelta.hasDiffs) {
260 statistics.commitsWithChangeInRootCount++;
261 _replayDiff(commitDelta);
262 }
263 changedPubspecs = commitDelta.filesMatching(PUBSPEC_FILE_NAME);
264 }
265 server.removeAllOverlays();
266 }
267
268 void _replayDiff(CommitDelta commitDelta) {
269 List<FileEdit> editList = <FileEdit>[];
270 for (DiffRecord record in commitDelta.diffRecords) {
271 FileEdit edit = new FileEdit(record);
272 _createSourceEdits(edit, record.getBlobDiff());
273 editList.add(edit);
274 }
275 // TODO(brianwilkerson) Randomize.
276 // Randomly select operations from different files to simulate a user
277 // editing multiple files simultaneously.
278 for (FileEdit edit in editList) {
279 List<String> currentFile = <String>[edit.filePath];
280 server.sendAnalysisSetPriorityFiles(currentFile);
281 server.sendAnalysisSetSubscriptions({
282 AnalysisService.FOLDING: currentFile,
283 AnalysisService.HIGHLIGHTS: currentFile,
284 AnalysisService.IMPLEMENTED: currentFile,
285 AnalysisService.NAVIGATION: currentFile,
286 AnalysisService.OCCURRENCES: currentFile,
287 AnalysisService.OUTLINE: currentFile,
288 AnalysisService.OVERRIDES: currentFile
289 });
290 for (ServerOperation operation in edit.getOperations()) {
291 operation.perform(server);
292 }
293 }
294 }
295
296 /**
297 * Run `pub` on the pubspec with the given [filePath].
298 */
299 void _runPub(String filePath) {
300 String directoryPath = path.dirname(filePath);
301 if (new Directory(directoryPath).existsSync()) {
302 Process.runSync(
303 '/Users/brianwilkerson/Dev/dart/dart-sdk/bin/pub', ['get'],
304 workingDirectory: directoryPath);
305 }
306 }
307
308 /**
309 * Display usage information, preceeded by the [errorMessage] if one is given.
310 */
311 void _showUsage(ArgParser parser, [String errorMessage = null]) {
312 if (errorMessage != null) {
313 stderr.writeln(errorMessage);
314 stderr.writeln();
315 }
316 stderr.writeln('''
317 Usage: replay [options...] repositoryPath analysisRoot...
318
319 Uses the commit history of the git repository at the given repository path to
320 simulate the development of a code base while using the analysis server to
321 analyze the code base.
322
323 The repository path must be the absolute path of a directory containing a git
324 repository.
325
326 There must be at least one analysis root, and all of the analysis roots must be
327 the absolute path of a directory contained within the repository directory. The
328 analysis roots represent the portion of the repository that will be analyzed by
329 the analysis server.
330
331 OPTIONS:''');
332 stderr.writeln(parser.usage);
333 }
334 }
335
336 /**
337 * A representation of the edits to be applied to a single file.
338 */
339 class FileEdit {
340 /**
341 * The absolute path of the file to be edited.
342 */
343 String filePath;
344
345 /**
346 * The content of the file before any edits have been applied.
347 */
348 String content;
349
350 /**
351 * The line info for the file before any edits have been applied.
352 */
353 LineInfo lineInfo;
354
355 /**
356 * The lists of source edits, one list for each hunk being edited.
357 */
358 List<List<SourceEdit>> editLists = <List<SourceEdit>>[];
359
360 /**
361 * Initialize a collection of edits to be associated with the file at the
362 * given [filePath].
363 */
364 FileEdit(DiffRecord record) {
365 filePath = record.srcPath;
366 if (record.isAddition) {
367 content = '';
368 lineInfo = new LineInfo(<int>[0]);
369 } else if (record.isCopy || record.isRename || record.isTypeChange) {
370 throw new ArgumentError('Unhandled change of type ${record.status}');
371 } else {
372 content = new File(filePath).readAsStringSync();
373 lineInfo = new LineInfo(StringUtilities.computeLineStarts(content));
374 }
375 }
376
377 /**
378 * Add a list of source edits that, taken together, transform a single hunk in
379 * the file.
380 */
381 void addSourceEdits(List<SourceEdit> sourceEdits) {
382 editLists.add(sourceEdits);
383 }
384
385 /**
386 * Return a list of operations to be sent to the server.
387 */
388 List<ServerOperation> getOperations() {
389 // TODO(brianwilkerson) Randomize.
390 // Make the order of edits random. Doing so will require updating the
391 // offsets of edits after the selected edit point.
392 List<ServerOperation> operations = <ServerOperation>[];
393 operations.add(
394 new AnalysisUpdateContent(filePath, new AddContentOverlay(content)));
395 for (List<SourceEdit> editList in editLists.reversed) {
396 for (SourceEdit edit in editList.reversed) {
397 operations.add(new AnalysisUpdateContent(
398 filePath, new ChangeContentOverlay([edit])));
399 }
400 }
401 operations
402 .add(new AnalysisUpdateContent(filePath, new RemoveContentOverlay()));
403 return operations;
404 }
405 }
406
407 /**
408 * A set of statistics related to the execution of the simulation.
409 */
410 class Statistics {
411 /**
412 * The driver driving the simulation.
413 */
414 final Driver driver;
415
416 /**
417 * The stopwatch being used to time the simulation.
418 */
419 Stopwatch stopwatch;
420
421 /**
422 * The total number of commits in the repository.
423 */
424 int commitCount;
425
426 /**
427 * The number of commits in the repository that touched one of the files in
428 * one of the analysis roots.
429 */
430 int commitsWithChangeInRootCount = 0;
431
432 /**
433 * Initialize a newly created set of statistics.
434 */
435 Statistics(this.driver);
436
437 void print() {
438 stdout.write('Replay commits in ');
439 stdout.writeln(driver.repositoryPath);
440 stdout.write(' replay took ');
441 stdout.writeln(_printTime(stopwatch.elapsedMilliseconds));
442 stdout.write(' analysis roots = ');
443 stdout.writeln(driver.analysisRoots);
444 stdout.write(' number of commits = ');
445 stdout.writeln(commitCount);
446 stdout.write(' number of commits with a change in an analysis root = ');
447 stdout.writeln(commitsWithChangeInRootCount);
448 }
449
450 String _printTime(int milliseconds) {
451 int seconds = milliseconds ~/ 1000;
452 milliseconds -= seconds * 1000;
453 int minutes = seconds ~/ 60;
454 seconds -= minutes * 60;
455 int hours = minutes ~/ 60;
456 minutes -= hours * 60;
457
458 if (hours > 0) {
459 return '$hours:$minutes:$seconds.$milliseconds';
460 } else if (minutes > 0) {
461 return '$minutes:$seconds.$milliseconds m';
462 } else if (seconds > 0) {
463 return '$seconds.$milliseconds s';
464 }
465 return '$milliseconds ms';
466 }
467 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698