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

Side by Side Diff: pkg/analysis_server/lib/src/status/get_handler2.dart

Issue 2530273004: Status page for the new analysis driver. (Closed)
Patch Set: Fixes for review comments. Created 4 years 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) 2014, the Dart project authors. Please see the AUTHORS file
Brian Wilkerson 2016/11/28 17:09:21 "2014" --> "2016"
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 import 'dart:convert';
6 import 'dart:io';
7 import 'dart:math';
8
9 import 'package:analysis_server/plugin/protocol/protocol.dart' hide Element;
10 import 'package:analysis_server/src/analysis_server.dart';
11 import 'package:analysis_server/src/domain_completion.dart';
12 import 'package:analysis_server/src/domain_diagnostic.dart';
13 import 'package:analysis_server/src/domain_execution.dart';
14 import 'package:analysis_server/src/services/completion/completion_performance.d art';
15 import 'package:analysis_server/src/socket_server.dart';
16 import 'package:analysis_server/src/status/get_handler.dart';
17 import 'package:analyzer/exception/exception.dart';
18 import 'package:analyzer/file_system/file_system.dart';
19 import 'package:analyzer/source/error_processor.dart';
20 import 'package:analyzer/source/sdk_ext.dart';
21 import 'package:analyzer/src/context/source.dart';
22 import 'package:analyzer/src/dart/analysis/driver.dart';
23 import 'package:analyzer/src/dart/sdk/sdk.dart';
24 import 'package:analyzer/src/generated/engine.dart';
25 import 'package:analyzer/src/generated/sdk.dart';
26 import 'package:analyzer/src/generated/source.dart';
27 import 'package:analyzer/src/generated/utilities_general.dart';
28 import 'package:analyzer/src/services/lint.dart';
29 import 'package:analyzer/task/model.dart';
30 import 'package:plugin/plugin.dart';
31
32 /**
33 * A function that can be used to generate HTML output into the given [buffer].
34 * The HTML that is generated must be valid (special characters must already be
35 * encoded).
36 */
37 typedef void HtmlGenerator(StringBuffer buffer);
38
39 /**
40 * Instances of the class [GetHandler2] handle GET requests.
41 */
42 class GetHandler2 implements AbstractGetHandler {
43 /**
44 * The path used to request overall performance information.
45 */
46 static const String ANALYSIS_PERFORMANCE_PATH = '/perf/analysis';
47
48 /**
49 * The path used to request code completion information.
50 */
51 static const String COMPLETION_PATH = '/completion';
52
53 /**
54 * The path used to request communication performance information.
55 */
56 static const String COMMUNICATION_PERFORMANCE_PATH = '/perf/communication';
57
58 /**
59 * The path used to request information about a specific context.
60 */
61 static const String CONTEXT_PATH = '/context';
62
63 /**
64 * The path used to request an overlay contents.
65 */
66 static const String OVERLAY_PATH = '/overlay';
67
68 /**
69 * The path used to request overlays information.
70 */
71 static const String OVERLAYS_PATH = '/overlays';
72
73 /**
74 * The path used to request the status of the analysis server as a whole.
75 */
76 static const String STATUS_PATH = '/status';
77
78 /**
79 * Query parameter used to represent the context to search for.
80 */
81 static const String CONTEXT_QUERY_PARAM = 'context';
82
83 /**
84 * Query parameter used to represent the path of an overlayed file.
85 */
86 static const String PATH_PARAM = 'path';
87
88 static final ContentType _htmlContent =
89 new ContentType("text", "html", charset: "utf-8");
90
91 /**
92 * The socket server whose status is to be reported on.
93 */
94 SocketServer _server;
95
96 /**
97 * Buffer containing strings printed by the analysis server.
98 */
99 List<String> _printBuffer;
100
101 /**
102 * Contents of overlay files.
103 */
104 final Map<String, String> _overlayContents = <String, String>{};
105
106 /**
107 * Handler for diagnostics requests.
108 */
109 DiagnosticDomainHandler _diagnosticHandler;
110
111 /**
112 * Initialize a newly created handler for GET requests.
113 */
114 GetHandler2(this._server, this._printBuffer);
115
116 DiagnosticDomainHandler get diagnosticHandler {
117 if (_diagnosticHandler == null) {
118 _diagnosticHandler = new DiagnosticDomainHandler(_server.analysisServer);
119 }
120 return _diagnosticHandler;
121 }
122
123 /**
124 * Return the active [CompletionDomainHandler]
125 * or `null` if either analysis server is not running
126 * or there is no completion domain handler.
127 */
128 CompletionDomainHandler get _completionDomainHandler {
129 AnalysisServer analysisServer = _server.analysisServer;
130 if (analysisServer == null) {
131 return null;
132 }
133 return analysisServer.handlers
134 .firstWhere((h) => h is CompletionDomainHandler, orElse: () => null);
135 }
136
137 /**
138 * Handle a GET request received by the HTTP server.
139 */
140 void handleGetRequest(HttpRequest request) {
141 String path = request.uri.path;
142 if (path == '/' || path == STATUS_PATH) {
143 _returnServerStatus(request);
144 } else if (path == ANALYSIS_PERFORMANCE_PATH) {
145 _returnAnalysisPerformance(request);
146 } else if (path == COMPLETION_PATH) {
147 _returnCompletionInfo(request);
148 } else if (path == COMMUNICATION_PERFORMANCE_PATH) {
149 _returnCommunicationPerformance(request);
150 } else if (path == CONTEXT_PATH) {
151 _returnContextInfo(request);
152 } else if (path == OVERLAY_PATH) {
153 _returnOverlayContents(request);
154 } else if (path == OVERLAYS_PATH) {
155 _returnOverlaysInfo(request);
156 } else {
157 _returnUnknownRequest(request);
158 }
159 }
160
161 /**
162 * Return the folder being managed by the given [analysisServer] that matches
163 * the given [contextFilter], or `null` if there is none.
164 */
165 Folder _findFolder(AnalysisServer analysisServer, String contextFilter) {
166 return analysisServer.driverMap.keys.firstWhere(
167 (Folder folder) => folder.path == contextFilter,
168 orElse: () => null);
169 }
170
171 /**
172 * Return `true` if the given analysis [driver] has at least one entry with
173 * an exception.
174 */
175 bool _hasException(AnalysisDriver driver) {
176 // if (driver == null) {
177 // return false;
178 // }
179 // MapIterator<AnalysisTarget, CacheEntry> iterator =
180 // context.analysisCache.iterator();
181 // while (iterator.moveNext()) {
182 // CacheEntry entry = iterator.value;
183 // if (entry == null || entry.exception != null) {
184 // return true;
185 // }
186 // }
187 // TODO(scheglov)
188 return false;
189 }
190
191 /**
192 * Return a response displaying overall performance information.
193 */
194 void _returnAnalysisPerformance(HttpRequest request) {
195 AnalysisServer analysisServer = _server.analysisServer;
196 if (analysisServer == null) {
197 return _returnFailure(request, 'Analysis server is not running');
198 }
199 _writeResponse(request, (StringBuffer buffer) {
200 _writePage(buffer, 'Analysis Server - Analysis Performance', [],
201 (StringBuffer buffer) {
202 buffer.write('<h3>Analysis Performance</h3>');
203 _writeTwoColumns(buffer, (StringBuffer buffer) {
204 //
205 // Write performance tags.
206 //
207 buffer.write('<p><b>Performance tag data</b></p>');
208 buffer.write(
209 '<table style="border-collapse: separate; border-spacing: 10px 5px ;">');
210 _writeRow(buffer, ['Time (in ms)', 'Percent', 'Tag name'],
211 header: true);
212 // prepare sorted tags
213 List<PerformanceTag> tags = PerformanceTag.all.toList();
214 tags.remove(ServerPerformanceStatistics.idle);
215 tags.sort((a, b) => b.elapsedMs - a.elapsedMs);
216 // prepare total time
217 int totalTagTime = 0;
218 tags.forEach((PerformanceTag tag) {
219 totalTagTime += tag.elapsedMs;
220 });
221 // write rows
222 void writeRow(PerformanceTag tag) {
223 double percent = (tag.elapsedMs * 100) / totalTagTime;
224 String percentStr = '${percent.toStringAsFixed(2)}%';
225 _writeRow(buffer, [tag.elapsedMs, percentStr, tag.label],
226 classes: ["right", "right", null]);
227 }
228
229 tags.forEach(writeRow);
230 buffer.write('</table>');
231 }, (StringBuffer buffer) {
232 //
233 // Write task model timing information.
234 //
235 buffer.write('<p><b>Task performance data</b></p>');
236 buffer.write(
237 '<table style="border-collapse: separate; border-spacing: 10px 5px ;">');
238 _writeRow(
239 buffer,
240 [
241 'Task Name',
242 'Count',
243 'Total Time (in ms)',
244 'Average Time (in ms)'
245 ],
246 header: true);
247
248 Map<Type, int> countMap = AnalysisTask.countMap;
249 Map<Type, Stopwatch> stopwatchMap = AnalysisTask.stopwatchMap;
250 List<Type> taskClasses = stopwatchMap.keys.toList();
251 taskClasses.sort((Type first, Type second) =>
252 first.toString().compareTo(second.toString()));
253 int totalTaskTime = 0;
254 taskClasses.forEach((Type taskClass) {
255 int count = countMap[taskClass];
256 if (count == null) {
257 count = 0;
258 }
259 int taskTime = stopwatchMap[taskClass].elapsedMilliseconds;
260 totalTaskTime += taskTime;
261 _writeRow(buffer, [
262 taskClass.toString(),
263 count,
264 taskTime,
265 count <= 0 ? '-' : (taskTime / count).toStringAsFixed(3)
266 ], classes: [
267 null,
268 "right",
269 "right",
270 "right"
271 ]);
272 });
273 _writeRow(buffer, ['Total', '-', totalTaskTime, '-'],
274 classes: [null, "right", "right", "right"]);
275 buffer.write('</table>');
276 });
277 });
278 });
279 }
280
281 /**
282 * Return a response displaying overall performance information.
283 */
284 void _returnCommunicationPerformance(HttpRequest request) {
285 AnalysisServer analysisServer = _server.analysisServer;
286 if (analysisServer == null) {
287 return _returnFailure(request, 'Analysis server is not running');
288 }
289 _writeResponse(request, (StringBuffer buffer) {
290 _writePage(buffer, 'Analysis Server - Communication Performance', [],
291 (StringBuffer buffer) {
292 buffer.write('<h3>Communication Performance</h3>');
293 _writeTwoColumns(buffer, (StringBuffer buffer) {
294 ServerPerformance perf = analysisServer.performanceDuringStartup;
295 int requestCount = perf.requestCount;
296 num averageLatency = requestCount > 0
297 ? (perf.requestLatency / requestCount).round()
298 : 0;
299 int maximumLatency = perf.maxLatency;
300 num slowRequestPercent = requestCount > 0
301 ? (perf.slowRequestCount * 100 / requestCount).round()
302 : 0;
303 buffer.write('<h4>Startup</h4>');
304 buffer.write('<table>');
305 _writeRow(buffer, [requestCount, 'requests'],
306 classes: ["right", null]);
307 _writeRow(buffer, [averageLatency, 'ms average latency'],
308 classes: ["right", null]);
309 _writeRow(buffer, [maximumLatency, 'ms maximum latency'],
310 classes: ["right", null]);
311 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'],
312 classes: ["right", null]);
313 if (analysisServer.performanceAfterStartup != null) {
314 int startupTime = analysisServer.performanceAfterStartup.startTime -
315 perf.startTime;
316 _writeRow(
317 buffer, [startupTime, 'ms for initial analysis to complete']);
318 }
319 buffer.write('</table>');
320 }, (StringBuffer buffer) {
321 ServerPerformance perf = analysisServer.performanceAfterStartup;
322 if (perf == null) {
323 return;
324 }
325 int requestCount = perf.requestCount;
326 num averageLatency = requestCount > 0
327 ? (perf.requestLatency * 10 / requestCount).round() / 10
328 : 0;
329 int maximumLatency = perf.maxLatency;
330 num slowRequestPercent = requestCount > 0
331 ? (perf.slowRequestCount * 100 / requestCount).round()
332 : 0;
333 buffer.write('<h4>Current</h4>');
334 buffer.write('<table>');
335 _writeRow(buffer, [requestCount, 'requests'],
336 classes: ["right", null]);
337 _writeRow(buffer, [averageLatency, 'ms average latency'],
338 classes: ["right", null]);
339 _writeRow(buffer, [maximumLatency, 'ms maximum latency'],
340 classes: ["right", null]);
341 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'],
342 classes: ["right", null]);
343 buffer.write('</table>');
344 });
345 });
346 });
347 }
348
349 /**
350 * Return a response displaying code completion information.
351 */
352 void _returnCompletionInfo(HttpRequest request) {
353 String value = request.requestedUri.queryParameters['index'];
354 int index = value != null ? int.parse(value, onError: (_) => 0) : 0;
355 _writeResponse(request, (StringBuffer buffer) {
356 _writePage(buffer, 'Analysis Server - Completion Stats', [],
357 (StringBuffer buffer) {
358 _writeCompletionPerformanceDetail(buffer, index);
359 _writeCompletionPerformanceList(buffer);
360 });
361 });
362 }
363
364 /**
365 * Return a response containing information about a single source file in the
366 * cache.
367 */
368 void _returnContextInfo(HttpRequest request) {
369 AnalysisServer analysisServer = _server.analysisServer;
370 if (analysisServer == null) {
371 return _returnFailure(request, 'Analysis server not running');
372 }
373 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM];
374 if (contextFilter == null) {
375 return _returnFailure(
376 request, 'Query parameter $CONTEXT_QUERY_PARAM required');
377 }
378 AnalysisDriver driver = null;
379 Folder folder = _findFolder(analysisServer, contextFilter);
380 if (folder == null) {
381 return _returnFailure(request, 'Invalid context: $contextFilter');
382 } else {
383 driver = analysisServer.driverMap[folder];
384 }
385
386 // TODO(scheglov) Show priority files.
387 // List<String> priorityNames = <String>[];
388 List<String> addedFiles = driver.addedFiles.toList();
389 List<String> implicitFiles =
390 driver.knownFiles.difference(driver.addedFiles).toList();
391 addedFiles.sort();
392 implicitFiles.sort();
393
394 // TODO(scheglov) Use file overlays.
395 // _overlayContents.clear();
396 // context.visitContentCache((String fullName, int stamp, String contents) {
397 // _overlayContents[fullName] = contents;
398 // });
399
400 void _writeFiles(StringBuffer buffer, String title, List<String> files) {
401 buffer.write('<h3>$title</h3>');
402 if (files == null || files.isEmpty) {
403 buffer.write('<p>None</p>');
404 } else {
405 buffer.write('<p><table style="width: 100%">');
406 for (String file in files) {
407 buffer.write('<tr><td>');
408 buffer.write(file);
409 buffer.write('</td><td>');
410 if (_overlayContents.containsKey(files)) {
411 buffer.write(makeLink(OVERLAY_PATH, {PATH_PARAM: file}, 'overlay'));
412 }
413 buffer.write('</td></tr>');
414 }
415 buffer.write('</table></p>');
416 }
417 }
418
419 void writeOptions(StringBuffer buffer, AnalysisOptionsImpl options,
420 {void writeAdditionalOptions(StringBuffer buffer)}) {
421 if (options == null) {
422 buffer.write('<p>No option information available.</p>');
423 return;
424 }
425 buffer.write('<p>');
426 _writeOption(
427 buffer, 'Analyze functon bodies', options.analyzeFunctionBodies);
428 _writeOption(
429 buffer, 'Enable strict call checks', options.enableStrictCallChecks);
430 _writeOption(buffer, 'Enable super mixins', options.enableSuperMixins);
431 _writeOption(buffer, 'Generate dart2js hints', options.dart2jsHint);
432 _writeOption(buffer, 'Generate errors in implicit files',
433 options.generateImplicitErrors);
434 _writeOption(
435 buffer, 'Generate errors in SDK files', options.generateSdkErrors);
436 _writeOption(buffer, 'Generate hints', options.hint);
437 _writeOption(buffer, 'Incremental resolution', options.incremental);
438 _writeOption(buffer, 'Incremental resolution with API changes',
439 options.incrementalApi);
440 _writeOption(buffer, 'Preserve comments', options.preserveComments);
441 _writeOption(buffer, 'Strong mode', options.strongMode);
442 _writeOption(buffer, 'Strong mode hints', options.strongModeHints);
443 if (writeAdditionalOptions != null) {
444 writeAdditionalOptions(buffer);
445 }
446 buffer.write('</p>');
447 }
448
449 _writeResponse(request, (StringBuffer buffer) {
450 _writePage(
451 buffer, 'Analysis Server - Context', ['Context: $contextFilter'],
452 (StringBuffer buffer) {
453 buffer.write('<h3>Configuration</h3>');
454
455 _writeColumns(buffer, <HtmlGenerator>[
456 (StringBuffer buffer) {
457 buffer.write('<p><b>Context Options</b></p>');
458 writeOptions(buffer, driver.analysisOptions);
459 },
460 (StringBuffer buffer) {
461 buffer.write('<p><b>SDK Context Options</b></p>');
462 DartSdk sdk = driver?.sourceFactory?.dartSdk;
463 writeOptions(buffer, sdk?.context?.analysisOptions,
464 writeAdditionalOptions: (StringBuffer buffer) {
465 if (sdk is FolderBasedDartSdk) {
466 _writeOption(buffer, 'Use summaries', sdk.useSummary);
467 }
468 });
469 },
470 (StringBuffer buffer) {
471 List<Linter> lints = driver.analysisOptions.lintRules;
472 buffer.write('<p><b>Lints</b></p>');
473 if (lints.isEmpty) {
474 buffer.write('<p>none</p>');
475 } else {
476 for (Linter lint in lints) {
477 buffer.write('<p>');
478 buffer.write(lint.runtimeType);
479 buffer.write('</p>');
480 }
481 }
482
483 List<ErrorProcessor> errorProcessors =
484 driver.analysisOptions.errorProcessors;
485 int processorCount = errorProcessors?.length ?? 0;
486 buffer
487 .write('<p><b>Error Processor count</b>: $processorCount</p>');
488 }
489 ]);
490
491 SourceFactory sourceFactory = driver.sourceFactory;
492 if (sourceFactory is SourceFactoryImpl) {
493 buffer.write('<h3>Resolvers</h3>');
494 for (UriResolver resolver in sourceFactory.resolvers) {
495 buffer.write('<p>');
496 buffer.write(resolver.runtimeType);
497 if (resolver is DartUriResolver) {
498 DartSdk sdk = resolver.dartSdk;
499 buffer.write(' (sdk = ');
500 buffer.write(sdk.runtimeType);
501 if (sdk is FolderBasedDartSdk) {
502 buffer.write(' (path = ');
503 buffer.write(sdk.directory.path);
504 buffer.write(')');
505 } else if (sdk is EmbedderSdk) {
506 buffer.write(' (map = ');
507 _writeMapOfStringToString(buffer, sdk.urlMappings);
508 buffer.write(')');
509 }
510 buffer.write(')');
511 } else if (resolver is SdkExtUriResolver) {
512 buffer.write(' (map = ');
513 _writeMapOfStringToString(buffer, resolver.urlMappings);
514 buffer.write(')');
515 }
516 buffer.write('</p>');
517 }
518 }
519
520 // TODO(scheglov) Show priority files.
521 // _writeFiles(
522 // buffer, 'Priority Files (${priorityNames.length})', priorityNames) ;
523 _writeFiles(buffer, 'Added Files (${addedFiles.length})', addedFiles);
524 _writeFiles(
525 buffer,
526 'Implicitly Analyzed Files (${implicitFiles.length})',
527 implicitFiles);
528
529 // TODO(scheglov) Show exceptions.
530 // buffer.write('<h3>Exceptions</h3>');
531 // if (exceptions.isEmpty) {
532 // buffer.write('<p>none</p>');
533 // } else {
534 // exceptions.forEach((CaughtException exception) {
535 // _writeException(buffer, exception);
536 // });
537 // }
538 });
539 });
540 }
541
542 void _returnFailure(HttpRequest request, String message) {
543 _writeResponse(request, (StringBuffer buffer) {
544 _writePage(buffer, 'Analysis Server - Failure', [],
545 (StringBuffer buffer) {
546 buffer.write(HTML_ESCAPE.convert(message));
547 });
548 });
549 }
550
551 void _returnOverlayContents(HttpRequest request) {
552 String path = request.requestedUri.queryParameters[PATH_PARAM];
553 if (path == null) {
554 return _returnFailure(request, 'Query parameter $PATH_PARAM required');
555 }
556 String contents = _overlayContents[path];
557
558 _writeResponse(request, (StringBuffer buffer) {
559 _writePage(buffer, 'Analysis Server - Overlay', [],
560 (StringBuffer buffer) {
561 buffer.write('<pre>${HTML_ESCAPE.convert(contents)}</pre>');
562 });
563 });
564 }
565
566 /**
567 * Return a response displaying overlays information.
568 */
569 void _returnOverlaysInfo(HttpRequest request) {
570 AnalysisServer analysisServer = _server.analysisServer;
571 if (analysisServer == null) {
572 return _returnFailure(request, 'Analysis server is not running');
573 }
574
575 _writeResponse(request, (StringBuffer buffer) {
576 _writePage(buffer, 'Analysis Server - Overlays', [],
577 (StringBuffer buffer) {
578 buffer.write('<table border="1">');
579 _overlayContents.clear();
580 ContentCache overlayState = analysisServer.overlayState;
581 overlayState.accept((String fullName, int stamp, String contents) {
582 buffer.write('<tr>');
583 String link =
584 makeLink(OVERLAY_PATH, {PATH_PARAM: fullName}, fullName);
585 DateTime time = new DateTime.fromMillisecondsSinceEpoch(stamp);
586 _writeRow(buffer, [link, time]);
587 _overlayContents[fullName] = contents;
588 });
589 int count = _overlayContents.length;
590 buffer.write('<tr><td colspan="2">Total: $count entries.</td></tr>');
591 buffer.write('</table>');
592 });
593 });
594 }
595
596 /**
597 * Return a response indicating the status of the analysis server.
598 */
599 void _returnServerStatus(HttpRequest request) {
600 _writeResponse(request, (StringBuffer buffer) {
601 _writePage(buffer, 'Analysis Server - Status', [], (StringBuffer buffer) {
602 if (_writeServerStatus(buffer)) {
603 _writeAnalysisStatus(buffer);
604 _writeEditStatus(buffer);
605 _writeExecutionStatus(buffer);
606 _writePluginStatus(buffer);
607 _writeRecentOutput(buffer);
608 }
609 });
610 });
611 }
612
613 /**
614 * Return an error in response to an unrecognized request received by the HTTP
615 * server.
616 */
617 void _returnUnknownRequest(HttpRequest request) {
618 _writeResponse(request, (StringBuffer buffer) {
619 _writePage(buffer, 'Analysis Server', [], (StringBuffer buffer) {
620 buffer.write('<h3>Unknown page: ');
621 buffer.write(request.uri.path);
622 buffer.write('</h3>');
623 buffer.write('''
624 <p>
625 You have reached an un-recognized page. If you reached this page by
626 following a link from a status page, please report the broken link to
627 the Dart analyzer team:
628 <a>https://github.com/dart-lang/sdk/issues/new</a>.
629 </p><p>
630 If you mistyped the URL, you can correct it or return to
631 ${makeLink(STATUS_PATH, {}, 'the main status page')}.
632 </p>''');
633 });
634 });
635 }
636
637 /**
638 * Return a two digit decimal representation of the given non-negative integer
639 * [value].
640 */
641 String _twoDigit(int value) {
642 if (value < 10) {
643 return '0$value';
644 }
645 return value.toString();
646 }
647
648 /**
649 * Write the status of the analysis domain (on the main status page) to the
650 * given [buffer] object.
651 */
652 void _writeAnalysisStatus(StringBuffer buffer) {
653 AnalysisServer analysisServer = _server.analysisServer;
654 Map<Folder, AnalysisDriver> driverMap = analysisServer.driverMap;
655 List<Folder> folders = driverMap.keys.toList();
656 folders.sort((Folder first, Folder second) =>
657 first.shortName.compareTo(second.shortName));
658
659 buffer.write('<h3>Analysis Domain</h3>');
660 _writeTwoColumns(buffer, (StringBuffer buffer) {
661 buffer.write('<p>Using package resolver provider: ');
662 buffer.write(_server.packageResolverProvider != null);
663 buffer.write('</p>');
664 buffer.write('<p>');
665 buffer.write(makeLink(OVERLAYS_PATH, {}, 'All overlay information'));
666 buffer.write('</p>');
667
668 buffer.write('<p><b>Analysis Contexts</b></p>');
669 buffer.write('<p>');
670 bool first = true;
671 folders.forEach((Folder folder) {
672 if (first) {
673 first = false;
674 } else {
675 buffer.write('<br>');
676 }
677 String key = folder.shortName;
678 buffer.write(makeLink(CONTEXT_PATH, {CONTEXT_QUERY_PARAM: folder.path},
679 key, _hasException(driverMap[folder])));
680 if (!folder.getChild('.packages').exists) {
681 buffer.write(' <small>[no .packages file]</small>');
682 }
683 });
684 buffer.write('</p>');
685
686 int freq = AnalysisServer.performOperationDelayFrequency;
687 String delay = freq > 0 ? '1 ms every $freq ms' : 'off';
688
689 buffer.write('<p><b>Performance Data</b></p>');
690 buffer.write('<p>Perform operation delay: $delay</p>');
691 buffer.write('<p>');
692 buffer.write(makeLink(ANALYSIS_PERFORMANCE_PATH, {}, 'Task data'));
693 buffer.write('</p>');
694 }, (StringBuffer buffer) {
695 _writeSubscriptionMap(
696 buffer, AnalysisService.VALUES, analysisServer.analysisServices);
697 });
698 }
699
700 /**
701 * Write multiple columns of information to the given [buffer], where the list
702 * of [columns] functions are used to generate the content of those columns.
703 */
704 void _writeColumns(StringBuffer buffer, List<HtmlGenerator> columns) {
705 buffer
706 .write('<table class="column"><tr class="column"><td class="column">');
707 int count = columns.length;
708 for (int i = 0; i < count; i++) {
709 if (i > 0) {
710 buffer.write('</td><td class="column">');
711 }
712 columns[i](buffer);
713 }
714 buffer.write('</td></tr></table>');
715 }
716
717 /**
718 * Write performance information about a specific completion request
719 * to the given [buffer] object.
720 */
721 void _writeCompletionPerformanceDetail(StringBuffer buffer, int index) {
722 CompletionDomainHandler handler = _completionDomainHandler;
723 CompletionPerformance performance;
724 if (handler != null) {
725 List<CompletionPerformance> list = handler.performanceList;
726 if (list != null && list.isNotEmpty) {
727 performance = list[max(0, min(list.length - 1, index))];
728 }
729 }
730 if (performance == null) {
731 buffer.write('<h3>Completion Performance Detail</h3>');
732 buffer.write('<p>No completions yet</p>');
733 return;
734 }
735 buffer.write('<h3>Completion Performance Detail</h3>');
736 buffer.write('<p>${performance.startTimeAndMs} for ${performance.source}');
737 buffer.write('<table>');
738 _writeRow(buffer, ['Elapsed', '', 'Operation'], header: true);
739 performance.operations.forEach((OperationPerformance op) {
740 String elapsed = op.elapsed != null ? op.elapsed.toString() : '???';
741 _writeRow(buffer, [elapsed, '&nbsp;&nbsp;', op.name]);
742 });
743 buffer.write('</table>');
744 buffer.write('<p><b>Compute Cache Performance</b>: ');
745 if (handler.computeCachePerformance == null) {
746 buffer.write('none');
747 } else {
748 int elapsed = handler.computeCachePerformance.elapsedInMilliseconds;
749 Source source = handler.computeCachePerformance.source;
750 buffer.write(' $elapsed ms for $source');
751 }
752 buffer.write('</p>');
753 }
754
755 /**
756 * Write a table showing summary information for the last several
757 * completion requests to the given [buffer] object.
758 */
759 void _writeCompletionPerformanceList(StringBuffer buffer) {
760 CompletionDomainHandler handler = _completionDomainHandler;
761 buffer.write('<h3>Completion Performance List</h3>');
762 if (handler == null) {
763 return;
764 }
765 buffer.write('<table>');
766 _writeRow(
767 buffer,
768 [
769 'Start Time',
770 '',
771 'First (ms)',
772 '',
773 'Complete (ms)',
774 '',
775 '# Notifications',
776 '',
777 '# Suggestions',
778 '',
779 'Snippet'
780 ],
781 header: true);
782 int index = 0;
783 for (CompletionPerformance performance in handler.performanceList) {
784 String link = makeLink(COMPLETION_PATH, {'index': '$index'},
785 '${performance.startTimeAndMs}');
786 _writeRow(buffer, [
787 link,
788 '&nbsp;&nbsp;',
789 performance.firstNotificationInMilliseconds,
790 '&nbsp;&nbsp;',
791 performance.elapsedInMilliseconds,
792 '&nbsp;&nbsp;',
793 performance.notificationCount,
794 '&nbsp;&nbsp;',
795 performance.suggestionCount,
796 '&nbsp;&nbsp;',
797 HTML_ESCAPE.convert(performance.snippet)
798 ]);
799 ++index;
800 }
801
802 buffer.write('</table>');
803 buffer.write('''
804 <p><strong>First (ms)</strong> - the number of milliseconds
805 from when completion received the request until the first notification
806 with completion results was queued for sending back to the client.
807 <p><strong>Complete (ms)</strong> - the number of milliseconds
808 from when completion received the request until the final notification
809 with completion results was queued for sending back to the client.
810 <p><strong># Notifications</strong> - the total number of notifications
811 sent to the client with completion results for this request.
812 <p><strong># Suggestions</strong> - the number of suggestions
813 sent to the client in the first notification, followed by a comma,
814 followed by the number of suggestions send to the client
815 in the last notification. If there is only one notification,
816 then there will be only one number in this column.''');
817 }
818
819 /**
820 * Write the status of the edit domain (on the main status page) to the given
821 * [buffer].
822 */
823 void _writeEditStatus(StringBuffer buffer) {
824 buffer.write('<h3>Edit Domain</h3>');
825 _writeTwoColumns(buffer, (StringBuffer buffer) {
826 buffer.write('<p><b>Performance Data</b></p>');
827 buffer.write('<p>');
828 buffer.write(makeLink(COMPLETION_PATH, {}, 'Completion data'));
829 buffer.write('</p>');
830 }, (StringBuffer buffer) {});
831 }
832
833 /**
834 * Write a representation of the given [caughtException] to the given
835 * [buffer]. If [isCause] is `true`, then the exception was a cause for
836 * another exception.
837 */
838 void _writeException(StringBuffer buffer, CaughtException caughtException,
839 {bool isCause: false}) {
840 Object exception = caughtException.exception;
841
842 if (exception is AnalysisException) {
843 buffer.write('<p>');
844 if (isCause) {
845 buffer.write('Caused by ');
846 }
847 buffer.write(exception.message);
848 buffer.write('</p>');
849 _writeStackTrace(buffer, caughtException.stackTrace);
850 CaughtException cause = exception.cause;
851 if (cause != null) {
852 buffer.write('<blockquote>');
853 _writeException(buffer, cause, isCause: true);
854 buffer.write('</blockquote>');
855 }
856 } else {
857 buffer.write('<p>');
858 if (isCause) {
859 buffer.write('Caused by ');
860 }
861 buffer.write(exception.toString());
862 buffer.write('<p>');
863 _writeStackTrace(buffer, caughtException.stackTrace);
864 }
865 }
866
867 /**
868 * Write the status of the execution domain (on the main status page) to the
869 * given [buffer].
870 */
871 void _writeExecutionStatus(StringBuffer buffer) {
872 AnalysisServer analysisServer = _server.analysisServer;
873 ExecutionDomainHandler handler = analysisServer.handlers.firstWhere(
874 (RequestHandler handler) => handler is ExecutionDomainHandler,
875 orElse: () => null);
876 Set<ExecutionService> services = new Set<ExecutionService>();
877 if (handler.onFileAnalyzed != null) {
878 services.add(ExecutionService.LAUNCH_DATA);
879 }
880
881 if (handler != null) {
882 buffer.write('<h3>Execution Domain</h3>');
883 _writeTwoColumns(buffer, (StringBuffer buffer) {
884 _writeSubscriptionList(buffer, ExecutionService.VALUES, services);
885 }, (StringBuffer buffer) {});
886 }
887 }
888
889 /**
890 * Write to the given [buffer] a representation of the given [map] of strings
891 * to strings.
892 */
893 void _writeMapOfStringToString(StringBuffer buffer, Map<String, String> map) {
894 List<String> keys = map.keys.toList();
895 keys.sort();
896 int length = keys.length;
897 buffer.write('{');
898 for (int i = 0; i < length; i++) {
899 buffer.write('<br>');
900 String key = keys[i];
901 if (i > 0) {
902 buffer.write(', ');
903 }
904 buffer.write(key);
905 buffer.write(' = ');
906 buffer.write(map[key]);
907 }
908 buffer.write('<br>}');
909 }
910
911 /**
912 * Write a representation of an analysis option with the given [name] and
913 * [value] to the given [buffer]. The option should be separated from other
914 * options unless the [last] flag is true, indicating that this is the last
915 * option in the list of options.
916 */
917 void _writeOption(StringBuffer buffer, String name, Object value,
918 {bool last: false}) {
919 buffer.write(name);
920 buffer.write(' = ');
921 buffer.write(value.toString());
922 if (!last) {
923 buffer.write('<br>');
924 }
925 }
926
927 /**
928 * Write a standard HTML page to the given [buffer]. The page will have the
929 * given [title] and a body that is generated by the given [body] generator.
930 */
931 void _writePage(StringBuffer buffer, String title, List<String> subtitles,
932 HtmlGenerator body) {
933 DateTime now = new DateTime.now();
934 String date = "${now.month}/${now.day}/${now.year}";
935 String time =
936 "${now.hour}:${_twoDigit(now.minute)}:${_twoDigit(now.second)}.${now.mil lisecond}";
937
938 buffer.write('<!DOCTYPE html>');
939 buffer.write('<html>');
940 buffer.write('<head>');
941 buffer.write('<meta charset="utf-8">');
942 buffer.write(
943 '<meta name="viewport" content="width=device-width, initial-scale=1.0">' );
944 buffer.write('<title>$title</title>');
945 buffer.write('<style>');
946 buffer.write('a {color: #0000DD; text-decoration: none;}');
947 buffer.write('a:link.error {background-color: #FFEEEE;}');
948 buffer.write('a:visited.error {background-color: #FFEEEE;}');
949 buffer.write('a:hover.error {background-color: #FFEEEE;}');
950 buffer.write('a:active.error {background-color: #FFEEEE;}');
951 buffer.write(
952 'h3 {background-color: #DDDDDD; margin-top: 0em; margin-bottom: 0em;}');
953 buffer.write('p {margin-top: 0.5em; margin-bottom: 0.5em;}');
954 buffer.write(
955 'p.commentary {margin-top: 1em; margin-bottom: 1em; margin-left: 2em; fo nt-style: italic;}');
956 // response.write('span.error {text-decoration-line: underline; text-decorati on-color: red; text-decoration-style: wavy;}');
957 buffer.write(
958 'table.column {border: 0px solid black; width: 100%; table-layout: fixed ;}');
959 buffer.write('td.column {vertical-align: top; width: 50%;}');
960 buffer.write('td.right {text-align: right;}');
961 buffer.write('th {text-align: left; vertical-align:top;}');
962 buffer.write('tr {vertical-align:top;}');
963 buffer.write('</style>');
964 buffer.write('</head>');
965
966 buffer.write('<body>');
967 buffer.write(
968 '<h2>$title <small><small>(as of $time on $date)</small></small></h2>');
969 if (subtitles != null && subtitles.isNotEmpty) {
970 buffer.write('<blockquote>');
971 bool first = true;
972 for (String subtitle in subtitles) {
973 if (first) {
974 first = false;
975 } else {
976 buffer.write('<br>');
977 }
978 buffer.write('<b>');
979 buffer.write(subtitle);
980 buffer.write('</b>');
981 }
982 buffer.write('</blockquote>');
983 }
984 try {
985 body(buffer);
986 } catch (exception, stackTrace) {
987 buffer.write('<h3>Exception while creating page</h3>');
988 _writeException(buffer, new CaughtException(exception, stackTrace));
989 }
990 buffer.write('</body>');
991 buffer.write('</html>');
992 }
993
994 /**
995 * Write the recent output section (on the main status page) to the given
996 * [buffer] object.
997 */
998 void _writePluginStatus(StringBuffer buffer) {
999 void writePlugin(Plugin plugin) {
1000 buffer.write(plugin.uniqueIdentifier);
1001 buffer.write(' (');
1002 buffer.write(plugin.runtimeType);
1003 buffer.write(')<br>');
1004 }
1005
1006 buffer.write('<h3>Plugin Status</h3><p>');
1007 writePlugin(AnalysisEngine.instance.enginePlugin);
1008 writePlugin(_server.serverPlugin);
1009 for (Plugin plugin in _server.analysisServer.userDefinedPlugins) {
1010 writePlugin(plugin);
1011 }
1012 buffer.write('<p>');
1013 }
1014
1015 /**
1016 * Write the recent output section (on the main status page) to the given
1017 * [buffer] object.
1018 */
1019 void _writeRecentOutput(StringBuffer buffer) {
1020 buffer.write('<h3>Recent Output</h3>');
1021 String output = HTML_ESCAPE.convert(_printBuffer.join('\n'));
1022 if (output.isEmpty) {
1023 buffer.write('<i>none</i>');
1024 } else {
1025 buffer.write('<pre>');
1026 buffer.write(output);
1027 buffer.write('</pre>');
1028 }
1029 }
1030
1031 void _writeResponse(HttpRequest request, HtmlGenerator writePage) {
1032 HttpResponse response = request.response;
1033 response.statusCode = HttpStatus.OK;
1034 response.headers.contentType = _htmlContent;
1035 try {
1036 StringBuffer buffer = new StringBuffer();
1037 try {
1038 writePage(buffer);
1039 } catch (exception, stackTrace) {
1040 buffer.clear();
1041 _writePage(buffer, 'Internal Exception', [], (StringBuffer buffer) {
1042 _writeException(buffer, new CaughtException(exception, stackTrace));
1043 });
1044 }
1045 response.write(buffer.toString());
1046 } finally {
1047 response.close();
1048 }
1049 }
1050
1051 /**
1052 * Write a single row within a table to the given [buffer]. The row will have
1053 * one cell for each of the [columns], and will be a header row if [header] is
1054 * `true`.
1055 */
1056 void _writeRow(StringBuffer buffer, List<Object> columns,
1057 {bool header: false, List<String> classes}) {
1058 buffer.write('<tr>');
1059 int count = columns.length;
1060 int maxClassIndex = classes == null ? 0 : classes.length - 1;
1061 for (int i = 0; i < count; i++) {
1062 String classAttribute = '';
1063 if (classes != null) {
1064 String className = classes[min(i, maxClassIndex)];
1065 if (className != null) {
1066 classAttribute = ' class="$className"';
1067 }
1068 }
1069 if (header) {
1070 buffer.write('<th$classAttribute>');
1071 } else {
1072 buffer.write('<td$classAttribute>');
1073 }
1074 buffer.write(columns[i]);
1075 if (header) {
1076 buffer.write('</th>');
1077 } else {
1078 buffer.write('</td>');
1079 }
1080 }
1081 buffer.write('</tr>');
1082 }
1083
1084 /**
1085 * Write the status of the service domain (on the main status page) to the
1086 * given [response] object.
1087 */
1088 bool _writeServerStatus(StringBuffer buffer) {
1089 AnalysisServer analysisServer = _server.analysisServer;
1090 Set<ServerService> services = analysisServer.serverServices;
1091
1092 buffer.write('<h3>Server Domain</h3>');
1093 _writeTwoColumns(buffer, (StringBuffer buffer) {
1094 if (analysisServer == null) {
1095 buffer.write('Status: <span style="color:red">Not running</span>');
1096 return;
1097 }
1098 buffer.write('<p>');
1099 buffer.write('Status: Running<br>');
1100 buffer.write('New analysis driver: ');
1101 buffer.write(analysisServer.options.enableNewAnalysisDriver);
1102 buffer.write('<br>');
1103 buffer.write('Instrumentation: ');
1104 if (AnalysisEngine.instance.instrumentationService.isActive) {
1105 buffer.write('<span style="color:red">Active</span>');
1106 } else {
1107 buffer.write('Inactive');
1108 }
1109 buffer.write('<br>');
1110 buffer.write('Version: ');
1111 buffer.write(AnalysisServer.VERSION);
1112 buffer.write('<br>');
1113 buffer.write('Process ID: ');
1114 buffer.write(pid);
1115 buffer.write('</p>');
1116
1117 buffer.write('<p><b>Performance Data</b></p>');
1118 buffer.write('<p>');
1119 buffer.write(makeLink(
1120 COMMUNICATION_PERFORMANCE_PATH, {}, 'Communication performance'));
1121 buffer.write('</p>');
1122 }, (StringBuffer buffer) {
1123 _writeSubscriptionList(buffer, ServerService.VALUES, services);
1124 });
1125 return analysisServer != null;
1126 }
1127
1128 /**
1129 * Write a representation of the given [stackTrace] to the given [buffer].
1130 */
1131 void _writeStackTrace(StringBuffer buffer, StackTrace stackTrace) {
1132 if (stackTrace != null) {
1133 String trace = stackTrace.toString().replaceAll('#', '<br>#');
1134 if (trace.startsWith('<br>#')) {
1135 trace = trace.substring(4);
1136 }
1137 buffer.write('<p>');
1138 buffer.write(trace);
1139 buffer.write('</p>');
1140 }
1141 }
1142
1143 /**
1144 * Given a [service] that could be subscribed to and a set of the services
1145 * that are actually subscribed to ([subscribedServices]), write a
1146 * representation of the service to the given [buffer].
1147 */
1148 void _writeSubscriptionInList(
1149 StringBuffer buffer, Enum service, Set<Enum> subscribedServices) {
1150 if (subscribedServices.contains(service)) {
1151 buffer.write('<code>+ </code>');
1152 } else {
1153 buffer.write('<code>- </code>');
1154 }
1155 buffer.write(service.name);
1156 buffer.write('<br>');
1157 }
1158
1159 /**
1160 * Given a [service] that could be subscribed to and a set of paths that are
1161 * subscribed to the services ([subscribedPaths]), write a representation of
1162 * the service to the given [buffer].
1163 */
1164 void _writeSubscriptionInMap(
1165 StringBuffer buffer, Enum service, Set<String> subscribedPaths) {
1166 buffer.write('<p>');
1167 buffer.write(service.name);
1168 buffer.write('</p>');
1169 if (subscribedPaths == null || subscribedPaths.isEmpty) {
1170 buffer.write('none');
1171 } else {
1172 List<String> paths = subscribedPaths.toList();
1173 paths.sort();
1174 for (String path in paths) {
1175 buffer.write('<p>');
1176 buffer.write(path);
1177 buffer.write('</p>');
1178 }
1179 }
1180 }
1181
1182 /**
1183 * Given a list containing all of the services that can be subscribed to in a
1184 * single domain ([allServices]) and a set of the services that are actually
1185 * subscribed to ([subscribedServices]), write a representation of the
1186 * subscriptions to the given [buffer].
1187 */
1188 void _writeSubscriptionList(StringBuffer buffer, List<Enum> allServices,
1189 Set<Enum> subscribedServices) {
1190 buffer.write('<p><b>Subscriptions</b></p>');
1191 buffer.write('<p>');
1192 for (Enum service in allServices) {
1193 _writeSubscriptionInList(buffer, service, subscribedServices);
1194 }
1195 buffer.write('</p>');
1196 }
1197
1198 /**
1199 * Given a list containing all of the services that can be subscribed to in a
1200 * single domain ([allServices]) and a set of the services that are actually
1201 * subscribed to ([subscribedServices]), write a representation of the
1202 * subscriptions to the given [buffer].
1203 */
1204 void _writeSubscriptionMap(StringBuffer buffer, List<Enum> allServices,
1205 Map<Enum, Set<String>> subscribedServices) {
1206 buffer.write('<p><b>Subscriptions</b></p>');
1207 for (Enum service in allServices) {
1208 _writeSubscriptionInMap(buffer, service, subscribedServices[service]);
1209 }
1210 }
1211
1212 /**
1213 * Write two columns of information to the given [buffer], where the
1214 * [leftColumn] and [rightColumn] functions are used to generate the content
1215 * of those columns.
1216 */
1217 void _writeTwoColumns(StringBuffer buffer, HtmlGenerator leftColumn,
1218 HtmlGenerator rightColumn) {
1219 buffer
1220 .write('<table class="column"><tr class="column"><td class="column">');
1221 leftColumn(buffer);
1222 buffer.write('</td><td class="column">');
1223 rightColumn(buffer);
1224 buffer.write('</td></tr></table>');
1225 }
1226
1227 /**
1228 * Create a link to [path] with query parameters [params], with inner HTML
1229 * [innerHtml]. If [hasError] is `true`, then the link will have the class
1230 * 'error'.
1231 */
1232 static String makeLink(
1233 String path, Map<String, String> params, String innerHtml,
1234 [bool hasError = false]) {
1235 Uri uri = params.isEmpty
1236 ? new Uri(path: path)
1237 : new Uri(path: path, queryParameters: params);
1238 String href = HTML_ESCAPE.convert(uri.toString());
1239 String classAttribute = hasError ? ' class="error"' : '';
1240 return '<a href="$href"$classAttribute>$innerHtml</a>';
1241 }
1242 }
OLDNEW
« no previous file with comments | « pkg/analysis_server/lib/src/status/get_handler.dart ('k') | pkg/analyzer/lib/src/dart/analysis/driver.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698