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

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

Powered by Google App Engine
This is Rietveld 408576698