| // Copyright (c) 2016, 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. |
| |
| library server.performance.scenarios; |
| |
| import 'dart:async'; |
| import 'dart:io'; |
| import 'dart:math'; |
| |
| import 'package:analysis_server/plugin/protocol/protocol.dart'; |
| import 'package:unittest/unittest.dart'; |
| |
| import 'performance_tests.dart'; |
| |
| void printBenchmarkResults(String id, String description, List<int> times) { |
| int minTime = times.fold(1 << 20, min); |
| String now = new DateTime.now().toUtc().toIso8601String(); |
| print('$now ========== $id'); |
| print('times: $times'); |
| print('min_time: $minTime'); |
| print(description.trim()); |
| print('--------------------'); |
| print(''); |
| print(''); |
| } |
| |
| class BenchmarkScenario extends AbstractTimingTest { |
| /** |
| * Init. |
| * - Start Analysis Server. |
| * - Set the analysis [roots]. |
| * - Wait for analysis to complete. |
| * - Make [file] the priority file. |
| * |
| * Measurement. |
| * - Change the [file] according to the [fileChange]. |
| * - Record the time to finish analysis. |
| * |
| * Repeat. |
| * - Undo changes to the [file]. |
| * - Repeat measurement [numOfRepeats] times. |
| */ |
| Future<List<int>> waitAnalyze_change_analyze( |
| {List<String> roots, |
| String file, |
| FileChange fileChange, |
| int numOfRepeats}) async { |
| expect(roots, isNotNull, reason: 'roots'); |
| expect(file, isNotNull, reason: 'file'); |
| expect(fileChange, isNotNull, reason: 'fileChange'); |
| expect(numOfRepeats, isNotNull, reason: 'numOfRepeats'); |
| // Initialize Analysis Server. |
| await super.setUp(); |
| await subscribeToStatusNotifications(); |
| // Set roots and analyze. |
| await sendAnalysisSetAnalysisRoots(roots, []); |
| await analysisFinished; |
| // Make the file priority. |
| await sendAnalysisSetPriorityFiles([file]); |
| // Repeat. |
| List<int> times = <int>[]; |
| for (int i = 0; i < numOfRepeats; i++) { |
| // Update and wait for analysis. |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| await _applyFileChange(file, fileChange); |
| await analysisFinished; |
| times.add(stopwatch.elapsed.inMilliseconds); |
| // Remove the overlay and analyze. |
| await sendAnalysisUpdateContent({file: new RemoveContentOverlay()}); |
| await analysisFinished; |
| } |
| // Done. |
| await shutdown(); |
| return times; |
| } |
| |
| /** |
| * Init. |
| * 1. Start Analysis Server. |
| * 2. Set the analysis [roots]. |
| * 3. Wait for analysis to complete. |
| * 4. Make [file] the priority file. |
| * |
| * Measurement. |
| * 5. Change the [file] according to the [fileChange]. |
| * 6. Request [completeAfterStr] in the updated file content. |
| * 7. Record the time to get completion results. |
| * 8. Undo changes to the [file] and analyze. |
| * 9. Go to (5). |
| */ |
| Future<List<int>> waitAnalyze_change_getCompletion( |
| {List<String> roots, |
| String file, |
| FileChange fileChange, |
| String completeAfterStr, |
| int numOfRepeats}) async { |
| expect(roots, isNotNull, reason: 'roots'); |
| expect(file, isNotNull, reason: 'file'); |
| expect(fileChange, isNotNull, reason: 'fileChange'); |
| expect(completeAfterStr, isNotNull, reason: 'completeAfterStr'); |
| expect(numOfRepeats, isNotNull, reason: 'numOfRepeats'); |
| // Initialize Analysis Server. |
| await super.setUp(); |
| await subscribeToStatusNotifications(); |
| // Set roots and analyze. |
| await sendAnalysisSetAnalysisRoots(roots, []); |
| await analysisFinished; |
| // Make the file priority. |
| await sendAnalysisSetPriorityFiles([file]); |
| // Repeat. |
| List<int> times = <int>[]; |
| for (int i = 0; i < numOfRepeats; i++) { |
| String updatedContent = await _applyFileChange(file, fileChange); |
| // Measure completion time. |
| int completionOffset = |
| _indexOfEnd(file, updatedContent, completeAfterStr); |
| Duration completionDuration = |
| await _measureCompletionTime(file, completionOffset); |
| times.add(completionDuration.inMilliseconds); |
| // Remove the overlay and analyze. |
| await sendAnalysisUpdateContent({file: new RemoveContentOverlay()}); |
| await analysisFinished; |
| } |
| // Done. |
| await shutdown(); |
| return times; |
| } |
| |
| /** |
| * Init. |
| * 1. Start Analysis Server. |
| * 2. Set the analysis [roots]. |
| * 3. Wait for analysis to complete. |
| * 4. Make [file] the priority file. |
| * |
| * Measurement. |
| * 5. Change the [file] according to the [fileChange]. |
| * 6. Request [refactoringAtStr] in the updated file content. |
| * 7. Record the time to get refactoring. |
| * 8. Undo changes to the [file] and analyze. |
| * 9. Go to (5). |
| */ |
| Future<List<int>> waitAnalyze_change_getRefactoring( |
| {List<String> roots, |
| String file, |
| FileChange fileChange, |
| String refactoringAtStr, |
| RefactoringKind refactoringKind, |
| RefactoringOptions refactoringOptions, |
| int numOfRepeats}) async { |
| expect(roots, isNotNull, reason: 'roots'); |
| expect(file, isNotNull, reason: 'file'); |
| expect(fileChange, isNotNull, reason: 'fileChange'); |
| expect(refactoringAtStr, isNotNull, reason: 'refactoringAtStr'); |
| expect(refactoringKind, isNotNull, reason: 'refactoringKind'); |
| expect(refactoringOptions, isNotNull, reason: 'refactoringOptions'); |
| expect(numOfRepeats, isNotNull, reason: 'numOfRepeats'); |
| // Initialize Analysis Server. |
| await super.setUp(); |
| await subscribeToStatusNotifications(); |
| // Set roots and analyze. |
| await sendAnalysisSetAnalysisRoots(roots, []); |
| await analysisFinished; |
| // Make the file priority. |
| await sendAnalysisSetPriorityFiles([file]); |
| // Repeat. |
| List<int> times = <int>[]; |
| for (int i = 0; i < numOfRepeats; i++) { |
| String updatedContent = await _applyFileChange(file, fileChange); |
| // Measure time to get refactoring. |
| int refactoringOffset = _indexOf(file, updatedContent, refactoringAtStr); |
| Duration refactoringDuration = await _measureRefactoringTime( |
| file, refactoringOffset, refactoringKind, refactoringOptions); |
| times.add(refactoringDuration.inMilliseconds); |
| // Remove the overlay and analyze. |
| await sendAnalysisUpdateContent({file: new RemoveContentOverlay()}); |
| await analysisFinished; |
| } |
| // Done. |
| await shutdown(); |
| return times; |
| } |
| |
| /** |
| * Compute updated content of the [file] as described by [desc], add overlay |
| * for the [file], and return the updated content. |
| */ |
| Future<String> _applyFileChange(String file, FileChange desc) async { |
| String originalContent = _getFileContent(file); |
| String updatedContent; |
| if (desc.afterStr != null) { |
| int offset = _indexOfEnd(file, originalContent, desc.afterStr); |
| offset -= desc.afterStrBack; |
| updatedContent = originalContent.substring(0, offset) + |
| desc.insertStr + |
| originalContent.substring(offset); |
| } else if (desc.replaceWhat != null) { |
| int offset = _indexOf(file, originalContent, desc.replaceWhat); |
| updatedContent = originalContent.substring(0, offset) + |
| desc.replaceWith + |
| originalContent.substring(offset + desc.replaceWhat.length); |
| } |
| await sendAnalysisUpdateContent( |
| {file: new AddContentOverlay(updatedContent)}); |
| return updatedContent; |
| } |
| |
| Future<Duration> _measureCompletionTime(String file, int offset) async { |
| Stopwatch stopwatch = new Stopwatch(); |
| stopwatch.start(); |
| Completer<Duration> completer = new Completer<Duration>(); |
| var completionSubscription = onCompletionResults.listen((_) { |
| completer.complete(stopwatch.elapsed); |
| }); |
| try { |
| await sendCompletionGetSuggestions(file, offset); |
| return await completer.future; |
| } finally { |
| completionSubscription.cancel(); |
| } |
| } |
| |
| Future<Duration> _measureRefactoringTime( |
| String file, |
| int offset, |
| RefactoringKind refactoringKind, |
| RefactoringOptions refactoringOptions) async { |
| Stopwatch stopwatch = new Stopwatch(); |
| stopwatch.start(); |
| await sendEditGetRefactoring(refactoringKind, file, offset, 0, false, |
| options: refactoringOptions); |
| return stopwatch.elapsed; |
| } |
| |
| /** |
| * 1. Start Analysis Server. |
| * 2. Set the analysis [roots]. |
| * 3. Wait for analysis to complete. |
| * 4. Record the time to finish analysis. |
| * 5. Shutdown. |
| * 6. Go to (1). |
| */ |
| static Future<List<int>> start_waitInitialAnalysis_shutdown( |
| {List<String> roots, int numOfRepeats}) async { |
| expect(roots, isNotNull, reason: 'roots'); |
| expect(numOfRepeats, isNotNull, reason: 'numOfRepeats'); |
| // Repeat. |
| List<int> times = <int>[]; |
| for (int i = 0; i < numOfRepeats; i++) { |
| BenchmarkScenario instance = new BenchmarkScenario(); |
| // Initialize Analysis Server. |
| await instance.setUp(); |
| await instance.subscribeToStatusNotifications(); |
| // Set roots and analyze. |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| await instance.sendAnalysisSetAnalysisRoots(roots, []); |
| await instance.analysisFinished; |
| times.add(stopwatch.elapsed.inMilliseconds); |
| // Stop the server. |
| await instance.shutdown(); |
| } |
| return times; |
| } |
| |
| static String _getFileContent(String path) { |
| File file = new File(path); |
| expect(file.existsSync(), isTrue, reason: 'File $path does not exist.'); |
| return file.readAsStringSync(); |
| } |
| |
| /** |
| * Return the index of [what] in [where] in the [file], fail if not found. |
| */ |
| static int _indexOf(String file, String where, String what) { |
| int index = where.indexOf(what); |
| expect(index, isNot(-1), reason: 'Cannot find |$what| in $file.'); |
| return index; |
| } |
| |
| /** |
| * Return the end index if [what] in [where] in the [file], fail if not found. |
| */ |
| static int _indexOfEnd(String file, String where, String what) { |
| return _indexOf(file, where, what) + what.length; |
| } |
| } |
| |
| class FileChange { |
| final String afterStr; |
| final int afterStrBack; |
| final String insertStr; |
| final String replaceWhat; |
| final String replaceWith; |
| |
| FileChange({this.afterStr, this.afterStrBack: 0, this.insertStr, this.replaceWhat, this.replaceWith}) { |
| if (afterStr != null) { |
| expect(insertStr, isNotNull, reason: 'insertStr'); |
| } else if (replaceWhat != null) { |
| expect(replaceWith, isNotNull, reason: 'replaceWith'); |
| } |
| } |
| } |