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

Side by Side Diff: pkg/analysis_server/lib/src/get_handler.dart

Issue 1490233007: Remove the old task model (Closed) Base URL: https://github.com/dart-lang/sdk.git@analyzer-breaking-0.27
Patch Set: Created 5 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 library analysis_server.src.get_handler;
6
7 import 'dart:async';
8 import 'dart:collection';
9 import 'dart:convert';
10 import 'dart:io';
11 import 'dart:math';
12
13 import 'package:analysis_server/plugin/protocol/protocol.dart' hide Element;
14 import 'package:analysis_server/src/analysis_server.dart';
15 import 'package:analysis_server/src/domain_completion.dart';
16 import 'package:analysis_server/src/domain_execution.dart';
17 import 'package:analysis_server/src/operation/operation.dart';
18 import 'package:analysis_server/src/operation/operation_analysis.dart';
19 import 'package:analysis_server/src/operation/operation_queue.dart';
20 import 'package:analysis_server/src/services/index/index.dart';
21 import 'package:analysis_server/src/services/index/local_index.dart';
22 import 'package:analysis_server/src/services/index/store/split_store.dart';
23 import 'package:analysis_server/src/socket_server.dart';
24 import 'package:analysis_server/src/status/ast_writer.dart';
25 import 'package:analysis_server/src/status/element_writer.dart';
26 import 'package:analyzer/file_system/file_system.dart';
27 import 'package:analyzer/src/generated/ast.dart';
28 import 'package:analyzer/src/generated/element.dart';
29 import 'package:analyzer/src/generated/engine.dart';
30 import 'package:analyzer/src/generated/java_engine.dart';
31 import 'package:analyzer/src/generated/source.dart';
32 import 'package:analyzer/src/generated/utilities_general.dart';
33 import 'package:analyzer/task/model.dart' as newTask;
34 import 'package:plugin/plugin.dart';
35
36 /**
37 * A function that can be used to generate HTML output into the given [buffer].
38 * The HTML that is generated must be valid (special characters must already be
39 * encoded).
40 */
41 typedef void HtmlGenerator(StringBuffer buffer);
42
43 /**
44 * Instances of the class [GetHandler] handle GET requests.
45 */
46 class GetHandler {
47 /**
48 * The path used to request overall performance information.
49 */
50 static const String ANALYSIS_PERFORMANCE_PATH = '/perf/analysis';
51
52 /**
53 * The path used to request information about a element model.
54 */
55 static const String AST_PATH = '/ast';
56
57 /**
58 * The path used to request information about the cache entry corresponding
59 * to a single file.
60 */
61 static const String CACHE_ENTRY_PATH = '/cache_entry';
62
63 /**
64 * The path used to request the list of source files in a certain cache
65 * state.
66 */
67 static const String CACHE_STATE_PATH = '/cache_state';
68
69 /**
70 * The path used to request code completion information.
71 */
72 static const String COMPLETION_PATH = '/completion';
73
74 /**
75 * The path used to request communication performance information.
76 */
77 static const String COMMUNICATION_PERFORMANCE_PATH = '/perf/communication';
78
79 /**
80 * The path used to request information about a specific context.
81 */
82 static const String CONTEXT_PATH = '/context';
83
84 /**
85 * The path used to request information about a element model.
86 */
87 static const String ELEMENT_PATH = '/element';
88
89 /**
90 * The path used to request information about elements with the given name.
91 */
92 static const String INDEX_ELEMENT_BY_NAME = '/index/element-by-name';
93
94 /**
95 * The path used to request an overlay contents.
96 */
97 static const String OVERLAY_PATH = '/overlay';
98
99 /**
100 * The path used to request overlays information.
101 */
102 static const String OVERLAYS_PATH = '/overlays';
103
104 /**
105 * The path used to request the status of the analysis server as a whole.
106 */
107 static const String STATUS_PATH = '/status';
108
109 /**
110 * Query parameter used to represent the context to search for, when
111 * accessing [CACHE_ENTRY_PATH] or [CACHE_STATE_PATH].
112 */
113 static const String CONTEXT_QUERY_PARAM = 'context';
114
115 /**
116 * Query parameter used to represent the descriptor to search for, when
117 * accessing [CACHE_STATE_PATH].
118 */
119 static const String DESCRIPTOR_QUERY_PARAM = 'descriptor';
120
121 /**
122 * Query parameter used to represent the name of elements to search for, when
123 * accessing [INDEX_ELEMENT_BY_NAME].
124 */
125 static const String INDEX_ELEMENT_NAME = 'name';
126
127 /**
128 * Query parameter used to represent the path of an overlayed file.
129 */
130 static const String PATH_PARAM = 'path';
131
132 /**
133 * Query parameter used to represent the source to search for, when accessing
134 * [CACHE_ENTRY_PATH].
135 */
136 static const String SOURCE_QUERY_PARAM = 'entry';
137
138 /**
139 * Query parameter used to represent the cache state to search for, when
140 * accessing [CACHE_STATE_PATH].
141 */
142 static const String STATE_QUERY_PARAM = 'state';
143
144 static final ContentType _htmlContent =
145 new ContentType("text", "html", charset: "utf-8");
146
147 /**
148 * The socket server whose status is to be reported on.
149 */
150 SocketServer _server;
151
152 /**
153 * Buffer containing strings printed by the analysis server.
154 */
155 List<String> _printBuffer;
156
157 /**
158 * Contents of overlay files.
159 */
160 final Map<String, String> _overlayContents = <String, String>{};
161
162 /**
163 * Initialize a newly created handler for GET requests.
164 */
165 GetHandler(this._server, this._printBuffer);
166
167 /**
168 * Return the active [CompletionDomainHandler]
169 * or `null` if either analysis server is not running
170 * or there is no completion domain handler.
171 */
172 CompletionDomainHandler get _completionDomainHandler {
173 AnalysisServer analysisServer = _server.analysisServer;
174 if (analysisServer == null) {
175 return null;
176 }
177 return analysisServer.handlers
178 .firstWhere((h) => h is CompletionDomainHandler, orElse: () => null);
179 }
180
181 /**
182 * Handle a GET request received by the HTTP server.
183 */
184 void handleGetRequest(HttpRequest request) {
185 String path = request.uri.path;
186 if (path == STATUS_PATH) {
187 _returnServerStatus(request);
188 } else if (path == ANALYSIS_PERFORMANCE_PATH) {
189 _returnAnalysisPerformance(request);
190 } else if (path == AST_PATH) {
191 _returnAst(request);
192 } else if (path == CACHE_STATE_PATH) {
193 _returnCacheState(request);
194 } else if (path == CACHE_ENTRY_PATH) {
195 _returnCacheEntry(request);
196 } else if (path == COMPLETION_PATH) {
197 _returnCompletionInfo(request);
198 } else if (path == COMMUNICATION_PERFORMANCE_PATH) {
199 _returnCommunicationPerformance(request);
200 } else if (path == CONTEXT_PATH) {
201 _returnContextInfo(request);
202 } else if (path == ELEMENT_PATH) {
203 _returnElement(request);
204 } else if (path == INDEX_ELEMENT_BY_NAME) {
205 _returnIndexElementByName(request);
206 } else if (path == OVERLAY_PATH) {
207 _returnOverlayContents(request);
208 } else if (path == OVERLAYS_PATH) {
209 _returnOverlaysInfo(request);
210 } else {
211 _returnUnknownRequest(request);
212 }
213 }
214
215 /**
216 * Return the folder being managed by the given [analysisServer] that matches
217 * the given [contextFilter], or `null` if there is none.
218 */
219 Folder _findFolder(AnalysisServer analysisServer, String contextFilter) {
220 return analysisServer.folderMap.keys.firstWhere(
221 (Folder folder) => folder.path == contextFilter,
222 orElse: () => null);
223 }
224
225 /**
226 * Return `true` if the given analysis [context] has at least one entry with
227 * an exception.
228 */
229 bool _hasException(AnalysisContextImpl context) {
230 bool hasException = false;
231 context.visitCacheItems((Source source, SourceEntry sourceEntry,
232 DataDescriptor rowDesc, CacheState state) {
233 if (sourceEntry.exception != null) {
234 hasException = true;
235 }
236 });
237 return hasException;
238 }
239
240 /**
241 * Return the folder in the [folderMap] with which the given [context] is
242 * associated.
243 */
244 Folder _keyForValue(
245 Map<Folder, AnalysisContext> folderMap, AnalysisContext context) {
246 for (Folder folder in folderMap.keys) {
247 if (folderMap[folder] == context) {
248 return folder;
249 }
250 }
251 return null;
252 }
253
254 /**
255 * Return a response displaying overall performance information.
256 */
257 void _returnAnalysisPerformance(HttpRequest request) {
258 AnalysisServer analysisServer = _server.analysisServer;
259 if (analysisServer == null) {
260 return _returnFailure(request, 'Analysis server is not running');
261 }
262 _writeResponse(request, (StringBuffer buffer) {
263 _writePage(buffer, 'Analysis Server - Analysis Performance', [],
264 (StringBuffer buffer) {
265 buffer.write('<h3>Analysis Performance</h3>');
266
267 //
268 // Write performance tags.
269 //
270 {
271 buffer.write('<p><b>Time spent in each phase of analysis</b></p>');
272 buffer.write(
273 '<table style="border-collapse: separate; border-spacing: 10px 5px ;">');
274 _writeRow(buffer, ['Time (in ms)', 'Percent', 'Analysis Phase'],
275 header: true);
276 // prepare sorted tags
277 List<PerformanceTag> tags = PerformanceTag.all.toList();
278 tags.remove(ServerPerformanceStatistics.idle);
279 tags.sort((a, b) => b.elapsedMs - a.elapsedMs);
280 // prepare total time
281 int totalTime = 0;
282 tags.forEach((PerformanceTag tag) {
283 totalTime += tag.elapsedMs;
284 });
285 // write rows
286 void writeRow(PerformanceTag tag) {
287 double percent = (tag.elapsedMs * 100) / totalTime;
288 String percentStr = '${percent.toStringAsFixed(2)}%';
289 _writeRow(buffer, [tag.elapsedMs, percentStr, tag.label],
290 classes: ["right", "right", null]);
291 }
292 tags.forEach(writeRow);
293 buffer.write('</table>');
294 }
295
296 //
297 // Write new task model timing information.
298 //
299 if (AnalysisEngine.instance.useTaskModel) {
300 buffer.write('<p><b>Task performace data</b></p>');
301 buffer.write(
302 '<table style="border-collapse: separate; border-spacing: 10px 5px ;">');
303 _writeRow(
304 buffer,
305 [
306 'Task Name',
307 'Count',
308 'Total Time (in ms)',
309 'Average Time (in ms)'
310 ],
311 header: true);
312
313 Map<Type, int> countMap = newTask.AnalysisTask.countMap;
314 Map<Type, Stopwatch> stopwatchMap = newTask.AnalysisTask.stopwatchMap;
315 List<Type> taskClasses = stopwatchMap.keys.toList();
316 taskClasses.sort((Type first, Type second) =>
317 first.toString().compareTo(second.toString()));
318 int totalTime = 0;
319 taskClasses.forEach((Type taskClass) {
320 int count = countMap[taskClass];
321 if (count == null) {
322 count = 0;
323 }
324 int taskTime = stopwatchMap[taskClass].elapsedMilliseconds;
325 totalTime += taskTime;
326 _writeRow(buffer, [
327 taskClass.toString(),
328 count,
329 taskTime,
330 count <= 0 ? '-' : (taskTime / count).toStringAsFixed(3)
331 ], classes: [
332 null,
333 "right",
334 "right",
335 "right"
336 ]);
337 });
338 _writeRow(buffer, ['Total', '-', totalTime, '-'],
339 classes: [null, "right", "right", "right"]);
340 buffer.write('</table>');
341 }
342
343 //
344 // Write old task model transition information.
345 //
346 {
347 Map<DataDescriptor, Map<CacheState, int>> transitionMap =
348 SourceEntry.transitionMap;
349 buffer.write(
350 '<p><b>Number of times a state transitioned to VALID (grouped by d escriptor)</b></p>');
351 if (transitionMap.isEmpty) {
352 buffer.write('<p>none</p>');
353 } else {
354 List<DataDescriptor> descriptors = transitionMap.keys.toList();
355 descriptors.sort((DataDescriptor first, DataDescriptor second) =>
356 first.toString().compareTo(second.toString()));
357 for (DataDescriptor key in descriptors) {
358 Map<CacheState, int> countMap = transitionMap[key];
359 List<CacheState> oldStates = countMap.keys.toList();
360 oldStates.sort((CacheState first, CacheState second) =>
361 first.toString().compareTo(second.toString()));
362 buffer.write('<p>${key.toString()}</p>');
363 buffer.write(
364 '<table style="border-collapse: separate; border-spacing: 10px 5px;">');
365 _writeRow(buffer, ['Count', 'Previous State'], header: true);
366 for (CacheState state in oldStates) {
367 _writeRow(buffer, [countMap[state], state.toString()],
368 classes: ["right", null]);
369 }
370 buffer.write('</table>');
371 }
372 }
373 }
374 });
375 });
376 }
377
378 /**
379 * Return a response containing information about an AST structure.
380 */
381 void _returnAst(HttpRequest request) {
382 AnalysisServer analysisServer = _server.analysisServer;
383 if (analysisServer == null) {
384 return _returnFailure(request, 'Analysis server not running');
385 }
386 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM];
387 if (contextFilter == null) {
388 return _returnFailure(
389 request, 'Query parameter $CONTEXT_QUERY_PARAM required');
390 }
391 Folder folder = _findFolder(analysisServer, contextFilter);
392 if (folder == null) {
393 return _returnFailure(request, 'Invalid context: $contextFilter');
394 }
395 String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM];
396 if (sourceUri == null) {
397 return _returnFailure(
398 request, 'Query parameter $SOURCE_QUERY_PARAM required');
399 }
400
401 AnalysisContextImpl context = analysisServer.folderMap[folder];
402
403 _writeResponse(request, (StringBuffer buffer) {
404 _writePage(buffer, 'Analysis Server - AST Structure',
405 ['Context: $contextFilter', 'File: $sourceUri'], (HttpResponse) {
406 Source source = context.sourceFactory.forUri(sourceUri);
407 if (source == null) {
408 buffer.write('<p>Not found.</p>');
409 return;
410 }
411 SourceEntry entry = context.getReadableSourceEntryOrNull(source);
412 if (entry == null) {
413 buffer.write('<p>Not found.</p>');
414 return;
415 }
416 CompilationUnit ast = (entry as DartEntry).anyParsedCompilationUnit;
417 if (ast == null) {
418 buffer.write('<p>null</p>');
419 return;
420 }
421 AstWriter writer = new AstWriter(buffer);
422 ast.accept(writer);
423 if (writer.exceptions.isNotEmpty) {
424 buffer.write('<h3>Exceptions while creating page</h3>');
425 for (CaughtException exception in writer.exceptions) {
426 _writeException(buffer, exception);
427 }
428 }
429 });
430 });
431 }
432
433 /**
434 * Return a response containing information about a single source file in the
435 * cache.
436 */
437 void _returnCacheEntry(HttpRequest request) {
438 AnalysisServer analysisServer = _server.analysisServer;
439 if (analysisServer == null) {
440 return _returnFailure(request, 'Analysis server not running');
441 }
442 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM];
443 if (contextFilter == null) {
444 return _returnFailure(
445 request, 'Query parameter $CONTEXT_QUERY_PARAM required');
446 }
447 Folder folder = _findFolder(analysisServer, contextFilter);
448 if (folder == null) {
449 return _returnFailure(request, 'Invalid context: $contextFilter');
450 }
451 String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM];
452 if (sourceUri == null) {
453 return _returnFailure(
454 request, 'Query parameter $SOURCE_QUERY_PARAM required');
455 }
456
457 List<Folder> allContexts = <Folder>[];
458 Map<Folder, SourceEntry> entryMap = new HashMap<Folder, SourceEntry>();
459 analysisServer.folderMap
460 .forEach((Folder folder, AnalysisContextImpl context) {
461 Source source = context.sourceFactory.forUri(sourceUri);
462 if (source != null) {
463 SourceEntry entry = context.getReadableSourceEntryOrNull(source);
464 if (entry != null) {
465 allContexts.add(folder);
466 entryMap[folder] = entry;
467 }
468 }
469 });
470 allContexts.sort((Folder firstFolder, Folder secondFolder) =>
471 firstFolder.path.compareTo(secondFolder.path));
472 AnalysisContextImpl context = analysisServer.folderMap[folder];
473
474 _writeResponse(request, (StringBuffer buffer) {
475 _writePage(buffer, 'Analysis Server - Cache Entry',
476 ['Context: $contextFilter', 'File: $sourceUri'], (HttpResponse) {
477 buffer.write('<h3>Analyzing Contexts</h3><p>');
478 bool first = true;
479 allContexts.forEach((Folder folder) {
480 if (first) {
481 first = false;
482 } else {
483 buffer.write('<br>');
484 }
485 AnalysisContextImpl analyzingContext =
486 analysisServer.folderMap[folder];
487 if (analyzingContext == context) {
488 buffer.write(folder.path);
489 } else {
490 buffer.write(makeLink(
491 CACHE_ENTRY_PATH,
492 {
493 CONTEXT_QUERY_PARAM: folder.path,
494 SOURCE_QUERY_PARAM: sourceUri
495 },
496 HTML_ESCAPE.convert(folder.path)));
497 }
498 if (entryMap[folder].explicitlyAdded) {
499 buffer.write(' (explicit)');
500 } else {
501 buffer.write(' (implicit)');
502 }
503 });
504 buffer.write('</p>');
505
506 SourceEntry entry = entryMap[folder];
507 if (entry == null) {
508 buffer.write('<p>Not being analyzed in this context.</p>');
509 return;
510 }
511 Map<String, String> linkParameters = <String, String>{
512 CONTEXT_QUERY_PARAM: folder.path,
513 SOURCE_QUERY_PARAM: sourceUri
514 };
515
516 buffer.write('<h3>Library Independent</h3>');
517 _writeDescriptorTable(buffer, entry.descriptors, entry.getState,
518 entry.getValue, linkParameters);
519 if (entry is DartEntry) {
520 for (Source librarySource in entry.containingLibraries) {
521 String libraryName = HTML_ESCAPE.convert(librarySource.fullName);
522 buffer.write('<h3>In library $libraryName:</h3>');
523 _writeDescriptorTable(
524 buffer,
525 entry.libraryDescriptors,
526 (DataDescriptor descriptor) =>
527 entry.getStateInLibrary(descriptor, librarySource),
528 (DataDescriptor descriptor) =>
529 entry.getValueInLibrary(descriptor, librarySource),
530 linkParameters);
531 }
532 }
533 if (entry.exception != null) {
534 buffer.write('<h3>Exception</h3>');
535 _writeException(buffer, entry.exception);
536 }
537 });
538 });
539 }
540
541 /**
542 * Return a response indicating the set of source files in a certain cache
543 * state.
544 */
545 void _returnCacheState(HttpRequest request) {
546 AnalysisServer analysisServer = _server.analysisServer;
547 if (analysisServer == null) {
548 return _returnFailure(request, 'Analysis server not running');
549 }
550 // Figure out which context is being searched within.
551 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM];
552 if (contextFilter == null) {
553 return _returnFailure(
554 request, 'Query parameter $CONTEXT_QUERY_PARAM required');
555 }
556 // Figure out what CacheState is being searched for.
557 String stateQueryParam = request.uri.queryParameters[STATE_QUERY_PARAM];
558 if (stateQueryParam == null) {
559 return _returnFailure(
560 request, 'Query parameter $STATE_QUERY_PARAM required');
561 }
562 CacheState stateFilter = null;
563 for (CacheState value in CacheState.values) {
564 if (value.toString() == stateQueryParam) {
565 stateFilter = value;
566 }
567 }
568 if (stateFilter == null) {
569 return _returnFailure(
570 request, 'Query parameter $STATE_QUERY_PARAM is invalid');
571 }
572 // Figure out which descriptor is being searched for.
573 String descriptorFilter =
574 request.uri.queryParameters[DESCRIPTOR_QUERY_PARAM];
575 if (descriptorFilter == null) {
576 return _returnFailure(
577 request, 'Query parameter $DESCRIPTOR_QUERY_PARAM required');
578 }
579
580 Folder folder = _findFolder(analysisServer, contextFilter);
581 AnalysisContextImpl context = analysisServer.folderMap[folder];
582 List<String> links = <String>[];
583 context.visitCacheItems((Source source, SourceEntry dartEntry,
584 DataDescriptor rowDesc, CacheState state) {
585 if (state != stateFilter || rowDesc.toString() != descriptorFilter) {
586 return;
587 }
588 String link = makeLink(
589 CACHE_ENTRY_PATH,
590 {
591 CONTEXT_QUERY_PARAM: folder.path,
592 SOURCE_QUERY_PARAM: source.uri.toString()
593 },
594 HTML_ESCAPE.convert(source.fullName));
595 links.add(link);
596 });
597
598 _writeResponse(request, (StringBuffer buffer) {
599 _writePage(buffer, 'Analysis Server - Cache Search', [
600 'Context: $contextFilter',
601 'Descriptor: ${HTML_ESCAPE.convert(descriptorFilter)}',
602 'State: ${HTML_ESCAPE.convert(stateQueryParam)}'
603 ], (StringBuffer buffer) {
604 buffer.write('<p>${links.length} files found</p>');
605 buffer.write('<ul>');
606 links.forEach((String link) {
607 buffer.write('<li>$link</li>');
608 });
609 buffer.write('</ul>');
610 });
611 });
612 }
613
614 /**
615 * Return a response displaying overall performance information.
616 */
617 void _returnCommunicationPerformance(HttpRequest request) {
618 AnalysisServer analysisServer = _server.analysisServer;
619 if (analysisServer == null) {
620 return _returnFailure(request, 'Analysis server is not running');
621 }
622 _writeResponse(request, (StringBuffer buffer) {
623 _writePage(buffer, 'Analysis Server - Communication Performance', [],
624 (StringBuffer buffer) {
625 buffer.write('<h3>Communication Performance</h3>');
626 _writeTwoColumns(buffer, (StringBuffer buffer) {
627 ServerPerformance perf = analysisServer.performanceDuringStartup;
628 int requestCount = perf.requestCount;
629 num averageLatency = requestCount > 0
630 ? (perf.requestLatency / requestCount).round()
631 : 0;
632 int maximumLatency = perf.maxLatency;
633 num slowRequestPercent = requestCount > 0
634 ? (perf.slowRequestCount * 100 / requestCount).round()
635 : 0;
636 buffer.write('<h4>Startup</h4>');
637 buffer.write('<table>');
638 _writeRow(buffer, [requestCount, 'requests'],
639 classes: ["right", null]);
640 _writeRow(buffer, [averageLatency, 'ms average latency'],
641 classes: ["right", null]);
642 _writeRow(buffer, [maximumLatency, 'ms maximum latency'],
643 classes: ["right", null]);
644 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'],
645 classes: ["right", null]);
646 if (analysisServer.performanceAfterStartup != null) {
647 int startupTime = analysisServer.performanceAfterStartup.startTime -
648 perf.startTime;
649 _writeRow(
650 buffer, [startupTime, 'ms for initial analysis to complete']);
651 }
652 buffer.write('</table>');
653 }, (StringBuffer buffer) {
654 ServerPerformance perf = analysisServer.performanceAfterStartup;
655 if (perf == null) {
656 return;
657 }
658 int requestCount = perf.requestCount;
659 num averageLatency = requestCount > 0
660 ? (perf.requestLatency * 10 / requestCount).round() / 10
661 : 0;
662 int maximumLatency = perf.maxLatency;
663 num slowRequestPercent = requestCount > 0
664 ? (perf.slowRequestCount * 100 / requestCount).round()
665 : 0;
666 buffer.write('<h4>Current</h4>');
667 buffer.write('<table>');
668 _writeRow(buffer, [requestCount, 'requests'],
669 classes: ["right", null]);
670 _writeRow(buffer, [averageLatency, 'ms average latency'],
671 classes: ["right", null]);
672 _writeRow(buffer, [maximumLatency, 'ms maximum latency'],
673 classes: ["right", null]);
674 _writeRow(buffer, [slowRequestPercent, '% > 150 ms latency'],
675 classes: ["right", null]);
676 buffer.write('</table>');
677 });
678 });
679 });
680 }
681
682 /**
683 * Return a response displaying code completion information.
684 */
685 void _returnCompletionInfo(HttpRequest request) {
686 String value = request.requestedUri.queryParameters['index'];
687 int index = value != null ? int.parse(value, onError: (_) => 0) : 0;
688 _writeResponse(request, (StringBuffer buffer) {
689 _writePage(buffer, 'Analysis Server - Completion Stats', [],
690 (StringBuffer buffer) {
691 _writeCompletionPerformanceDetail(buffer, index);
692 _writeCompletionPerformanceList(buffer);
693 });
694 });
695 }
696
697 /**
698 * Return a response containing information about a single source file in the
699 * cache.
700 */
701 void _returnContextInfo(HttpRequest request) {
702 AnalysisServer analysisServer = _server.analysisServer;
703 if (analysisServer == null) {
704 return _returnFailure(request, 'Analysis server not running');
705 }
706 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM];
707 if (contextFilter == null) {
708 return _returnFailure(
709 request, 'Query parameter $CONTEXT_QUERY_PARAM required');
710 }
711 Folder folder = _findFolder(analysisServer, contextFilter);
712 if (folder == null) {
713 return _returnFailure(request, 'Invalid context: $contextFilter');
714 }
715
716 List<String> priorityNames;
717 List<String> explicitNames = <String>[];
718 List<String> implicitNames = <String>[];
719 Map<String, String> links = new HashMap<String, String>();
720 List<CaughtException> exceptions = <CaughtException>[];
721 AnalysisContextImpl context = analysisServer.folderMap[folder];
722 priorityNames = context.prioritySources
723 .map((Source source) => source.fullName)
724 .toList();
725 context.visitCacheItems((Source source, SourceEntry sourceEntry,
726 DataDescriptor rowDesc, CacheState state) {
727 String sourceName = source.fullName;
728 if (!links.containsKey(sourceName)) {
729 CaughtException exception = sourceEntry.exception;
730 if (exception != null) {
731 exceptions.add(exception);
732 }
733 String link = makeLink(
734 CACHE_ENTRY_PATH,
735 {
736 CONTEXT_QUERY_PARAM: folder.path,
737 SOURCE_QUERY_PARAM: source.uri.toString()
738 },
739 sourceName,
740 exception != null);
741 if (sourceEntry.explicitlyAdded) {
742 explicitNames.add(sourceName);
743 } else {
744 implicitNames.add(sourceName);
745 }
746 links[sourceName] = link;
747 }
748 });
749 explicitNames.sort();
750 implicitNames.sort();
751
752 _overlayContents.clear();
753 context.visitContentCache((String fullName, int stamp, String contents) {
754 _overlayContents[fullName] = contents;
755 });
756
757 void _writeFiles(
758 StringBuffer buffer, String title, List<String> fileNames) {
759 buffer.write('<h3>$title</h3>');
760 if (fileNames == null || fileNames.isEmpty) {
761 buffer.write('<p>None</p>');
762 } else {
763 buffer.write('<table style="width: 100%">');
764 for (String fileName in fileNames) {
765 buffer.write('<tr><td>');
766 buffer.write(links[fileName]);
767 buffer.write('</td><td>');
768 if (_overlayContents.containsKey(fileName)) {
769 buffer.write(
770 makeLink(OVERLAY_PATH, {PATH_PARAM: fileName}, 'overlay'));
771 }
772 buffer.write('</td></tr>');
773 }
774 buffer.write('</table>');
775 }
776 }
777
778 _writeResponse(request, (StringBuffer buffer) {
779 _writePage(
780 buffer, 'Analysis Server - Context', ['Context: $contextFilter'],
781 (StringBuffer buffer) {
782 List headerRowText = ['Context'];
783 headerRowText.addAll(CacheState.values);
784 buffer.write('<h3>Summary</h3>');
785 buffer.write('<table>');
786 _writeRow(buffer, headerRowText, header: true);
787 AnalysisContextStatistics statistics = context.statistics;
788 statistics.cacheRows.forEach((AnalysisContextStatistics_CacheRow row) {
789 List rowText = [row.name];
790 for (CacheState state in CacheState.values) {
791 String text = row.getCount(state).toString();
792 Map<String, String> params = <String, String>{
793 STATE_QUERY_PARAM: state.toString(),
794 CONTEXT_QUERY_PARAM: folder.path,
795 DESCRIPTOR_QUERY_PARAM: row.name
796 };
797 rowText.add(makeLink(CACHE_STATE_PATH, params, text));
798 }
799 _writeRow(buffer, rowText, classes: [null, "right"]);
800 });
801 buffer.write('</table>');
802
803 _writeFiles(buffer, 'Priority Files', priorityNames);
804 _writeFiles(buffer, 'Explicitly Analyzed Files', explicitNames);
805 _writeFiles(buffer, 'Implicitly Analyzed Files', implicitNames);
806
807 buffer.write('<h3>Exceptions</h3>');
808 if (exceptions.isEmpty) {
809 buffer.write('<p>None</p>');
810 } else {
811 exceptions.forEach((CaughtException exception) {
812 _writeException(buffer, exception);
813 });
814 }
815 });
816 });
817 }
818
819 /**
820 * Return a response containing information about an element structure.
821 */
822 void _returnElement(HttpRequest request) {
823 AnalysisServer analysisServer = _server.analysisServer;
824 if (analysisServer == null) {
825 return _returnFailure(request, 'Analysis server not running');
826 }
827 String contextFilter = request.uri.queryParameters[CONTEXT_QUERY_PARAM];
828 if (contextFilter == null) {
829 return _returnFailure(
830 request, 'Query parameter $CONTEXT_QUERY_PARAM required');
831 }
832 Folder folder = _findFolder(analysisServer, contextFilter);
833 if (folder == null) {
834 return _returnFailure(request, 'Invalid context: $contextFilter');
835 }
836 String sourceUri = request.uri.queryParameters[SOURCE_QUERY_PARAM];
837 if (sourceUri == null) {
838 return _returnFailure(
839 request, 'Query parameter $SOURCE_QUERY_PARAM required');
840 }
841
842 AnalysisContextImpl context = analysisServer.folderMap[folder];
843
844 _writeResponse(request, (StringBuffer buffer) {
845 _writePage(buffer, 'Analysis Server - Element Model', [
846 'Context: $contextFilter',
847 'File: $sourceUri'
848 ], (StringBuffer buffer) {
849 Source source = context.sourceFactory.forUri(sourceUri);
850 if (source == null) {
851 buffer.write('<p>Not found.</p>');
852 return;
853 }
854 SourceEntry entry = context.getReadableSourceEntryOrNull(source);
855 if (entry == null) {
856 buffer.write('<p>Not found.</p>');
857 return;
858 }
859 LibraryElement element = entry.getValue(DartEntry.ELEMENT);
860 if (element == null) {
861 buffer.write('<p>null</p>');
862 return;
863 }
864 element.accept(new ElementWriter(buffer));
865 });
866 });
867 }
868
869 void _returnFailure(HttpRequest request, String message) {
870 _writeResponse(request, (StringBuffer buffer) {
871 _writePage(buffer, 'Analysis Server - Failure', [],
872 (StringBuffer buffer) {
873 buffer.write(HTML_ESCAPE.convert(message));
874 });
875 });
876 }
877
878 /**
879 * Return a response containing information about elements with the given
880 * name.
881 */
882 Future _returnIndexElementByName(HttpRequest request) async {
883 AnalysisServer analysisServer = _server.analysisServer;
884 if (analysisServer == null) {
885 return _returnFailure(request, 'Analysis server not running');
886 }
887 Index index = analysisServer.index;
888 if (index == null) {
889 return _returnFailure(request, 'Indexing is disabled');
890 }
891 String name = request.uri.queryParameters[INDEX_ELEMENT_NAME];
892 if (name == null) {
893 return _returnFailure(
894 request, 'Query parameter $INDEX_ELEMENT_NAME required');
895 }
896 if (index is LocalIndex) {
897 Map<List<String>, List<InspectLocation>> relations =
898 await index.findElementsByName(name);
899 _writeResponse(request, (StringBuffer buffer) {
900 _writePage(buffer, 'Analysis Server - Index Elements', ['Name: $name'],
901 (StringBuffer buffer) {
902 buffer.write('<table border="1">');
903 _writeRow(buffer, ['Element', 'Relationship', 'Location'],
904 header: true);
905 relations.forEach(
906 (List<String> elementPath, List<InspectLocation> relations) {
907 String elementLocation = elementPath.join(' ');
908 relations.forEach((InspectLocation location) {
909 var relString = location.relationship.identifier;
910 var locString = '${location.path} offset=${location.offset} '
911 'length=${location.length} flags=${location.flags}';
912 _writeRow(buffer, [elementLocation, relString, locString]);
913 });
914 });
915 buffer.write('</table>');
916 });
917 });
918 } else {
919 return _returnFailure(request, 'LocalIndex expected, but $index found.');
920 }
921 }
922
923 void _returnOverlayContents(HttpRequest request) {
924 String path = request.requestedUri.queryParameters[PATH_PARAM];
925 if (path == null) {
926 return _returnFailure(request, 'Query parameter $PATH_PARAM required');
927 }
928 String contents = _overlayContents[path];
929
930 _writeResponse(request, (StringBuffer buffer) {
931 _writePage(buffer, 'Analysis Server - Overlay', [],
932 (StringBuffer buffer) {
933 buffer.write('<pre>${HTML_ESCAPE.convert(contents)}</pre>');
934 });
935 });
936 }
937
938 /**
939 * Return a response displaying overlays information.
940 */
941 void _returnOverlaysInfo(HttpRequest request) {
942 AnalysisServer analysisServer = _server.analysisServer;
943 if (analysisServer == null) {
944 return _returnFailure(request, 'Analysis server is not running');
945 }
946
947 _writeResponse(request, (StringBuffer buffer) {
948 _writePage(buffer, 'Analysis Server - Overlays', [],
949 (StringBuffer buffer) {
950 buffer.write('<table border="1">');
951 _overlayContents.clear();
952 ContentCache overlayState = analysisServer.overlayState;
953 overlayState.accept((String fullName, int stamp, String contents) {
954 buffer.write('<tr>');
955 String link =
956 makeLink(OVERLAY_PATH, {PATH_PARAM: fullName}, fullName);
957 DateTime time = new DateTime.fromMillisecondsSinceEpoch(stamp);
958 _writeRow(buffer, [link, time]);
959 _overlayContents[fullName] = contents;
960 });
961 int count = _overlayContents.length;
962 buffer.write('<tr><td colspan="2">Total: $count entries.</td></tr>');
963 buffer.write('</table>');
964 });
965 });
966 }
967
968 /**
969 * Return a response indicating the status of the analysis server.
970 */
971 void _returnServerStatus(HttpRequest request) {
972 _writeResponse(request, (StringBuffer buffer) {
973 _writePage(buffer, 'Analysis Server - Status', [], (StringBuffer buffer) {
974 if (_writeServerStatus(buffer)) {
975 _writeAnalysisStatus(buffer);
976 _writeEditStatus(buffer);
977 _writeExecutionStatus(buffer);
978 _writePluginStatus(buffer);
979 _writeRecentOutput(buffer);
980 }
981 });
982 });
983 }
984
985 /**
986 * Return an error in response to an unrecognized request received by the HTTP
987 * server.
988 */
989 void _returnUnknownRequest(HttpRequest request) {
990 _writeResponse(request, (StringBuffer buffer) {
991 _writePage(buffer, 'Analysis Server', [], (StringBuffer buffer) {
992 buffer.write('<h3>Pages</h3>');
993 buffer.write('<p>');
994 buffer.write(makeLink(COMPLETION_PATH, {}, 'Completion data'));
995 buffer.write('</p>');
996 buffer.write('<p>');
997 buffer
998 .write(makeLink(COMMUNICATION_PERFORMANCE_PATH, {}, 'Performance'));
999 buffer.write('</p>');
1000 buffer.write('<p>');
1001 buffer.write(makeLink(STATUS_PATH, {}, 'Server status'));
1002 buffer.write('</p>');
1003 buffer.write('<p>');
1004 buffer.write(makeLink(OVERLAYS_PATH, {}, 'File overlays'));
1005 buffer.write('</p>');
1006 });
1007 });
1008 }
1009
1010 /**
1011 * Return a two digit decimal representation of the given non-negative integer
1012 * [value].
1013 */
1014 String _twoDigit(int value) {
1015 if (value < 10) {
1016 return '0$value';
1017 }
1018 return value.toString();
1019 }
1020
1021 /**
1022 * Write the status of the analysis domain (on the main status page) to the
1023 * given [buffer] object.
1024 */
1025 void _writeAnalysisStatus(StringBuffer buffer) {
1026 AnalysisServer analysisServer = _server.analysisServer;
1027 Map<Folder, AnalysisContext> folderMap = analysisServer.folderMap;
1028 List<Folder> folders = folderMap.keys.toList();
1029 folders.sort((Folder first, Folder second) =>
1030 first.shortName.compareTo(second.shortName));
1031 AnalysisOptionsImpl options = analysisServer.defaultContextOptions;
1032 ServerOperationQueue operationQueue = analysisServer.operationQueue;
1033
1034 buffer.write('<h3>Analysis Domain</h3>');
1035 _writeTwoColumns(buffer, (StringBuffer buffer) {
1036 if (operationQueue.isEmpty) {
1037 buffer.write('<p>Status: Done analyzing</p>');
1038 } else {
1039 ServerOperation operation = operationQueue.peek();
1040 if (operation is PerformAnalysisOperation) {
1041 Folder folder = _keyForValue(folderMap, operation.context);
1042 if (folder == null) {
1043 buffer.write('<p>Status: Analyzing in unmapped context</p>');
1044 } else {
1045 buffer.write('<p>Status: Analyzing in ${folder.path}</p>');
1046 }
1047 } else {
1048 buffer.write('<p>Status: Analyzing</p>');
1049 }
1050 }
1051
1052 buffer.write('<p><b>Analysis Contexts</b></p>');
1053 buffer.write('<p>');
1054 bool first = true;
1055 folders.forEach((Folder folder) {
1056 if (first) {
1057 first = false;
1058 } else {
1059 buffer.write('<br>');
1060 }
1061 String key = folder.shortName;
1062 buffer.write(makeLink(CONTEXT_PATH, {CONTEXT_QUERY_PARAM: folder.path},
1063 key, _hasException(folderMap[folder])));
1064 });
1065 buffer.write('</p>');
1066
1067 buffer.write('<p><b>Options</b></p>');
1068 buffer.write('<p>');
1069 _writeOption(
1070 buffer, 'Analyze functon bodies', options.analyzeFunctionBodies);
1071 _writeOption(buffer, 'Cache size', options.cacheSize);
1072 _writeOption(
1073 buffer, 'Enable strict call checks', options.enableStrictCallChecks);
1074 _writeOption(buffer, 'Enable super mixins', options.enableSuperMixins);
1075 _writeOption(buffer, 'Generate hints', options.hint);
1076 _writeOption(buffer, 'Generate dart2js hints', options.dart2jsHint);
1077 _writeOption(buffer, 'Generate errors in implicit files',
1078 options.generateImplicitErrors);
1079 _writeOption(
1080 buffer, 'Generate errors in SDK files', options.generateSdkErrors);
1081 _writeOption(buffer, 'Incremental resolution', options.incremental);
1082 _writeOption(buffer, 'Incremental resolution with API changes',
1083 options.incrementalApi);
1084 _writeOption(buffer, 'Preserve comments', options.preserveComments,
1085 last: true);
1086 buffer.write('</p>');
1087 int freq = AnalysisServer.performOperationDelayFreqency;
1088 String delay = freq > 0 ? '1 ms every $freq ms' : 'off';
1089 buffer.write('<p><b>perform operation delay:</b> $delay</p>');
1090
1091 buffer.write('<p><b>Performance Data</b></p>');
1092 buffer.write('<p>');
1093 buffer.write(makeLink(ANALYSIS_PERFORMANCE_PATH, {}, 'Task data'));
1094 buffer.write('</p>');
1095 }, (StringBuffer buffer) {
1096 _writeSubscriptionMap(
1097 buffer, AnalysisService.VALUES, analysisServer.analysisServices);
1098 });
1099 }
1100
1101 /**
1102 * Write performance information about a specific completion request
1103 * to the given [buffer] object.
1104 */
1105 void _writeCompletionPerformanceDetail(StringBuffer buffer, int index) {
1106 CompletionDomainHandler handler = _completionDomainHandler;
1107 CompletionPerformance performance;
1108 if (handler != null) {
1109 List<CompletionPerformance> list = handler.performanceList;
1110 if (list != null && list.isNotEmpty) {
1111 performance = list[max(0, min(list.length - 1, index))];
1112 }
1113 }
1114 if (performance == null) {
1115 buffer.write('<h3>Completion Performance Detail</h3>');
1116 buffer.write('<p>No completions yet</p>');
1117 return;
1118 }
1119 buffer.write('<h3>Completion Performance Detail</h3>');
1120 buffer.write('<p>${performance.startTimeAndMs} for ${performance.source}');
1121 buffer.write('<table>');
1122 _writeRow(buffer, ['Elapsed', '', 'Operation'], header: true);
1123 performance.operations.forEach((OperationPerformance op) {
1124 String elapsed = op.elapsed != null ? op.elapsed.toString() : '???';
1125 _writeRow(buffer, [elapsed, '&nbsp;&nbsp;', op.name]);
1126 });
1127 buffer.write('</table>');
1128 buffer.write('<p><b>Compute Cache Performance</b>: ');
1129 if (handler.computeCachePerformance == null) {
1130 buffer.write('none');
1131 } else {
1132 int elapsed = handler.computeCachePerformance.elapsedInMilliseconds;
1133 Source source = handler.computeCachePerformance.source;
1134 buffer.write(' $elapsed ms for $source');
1135 }
1136 buffer.write('</p>');
1137 }
1138
1139 /**
1140 * Write a table showing summary information for the last several
1141 * completion requests to the given [buffer] object.
1142 */
1143 void _writeCompletionPerformanceList(StringBuffer buffer) {
1144 CompletionDomainHandler handler = _completionDomainHandler;
1145 buffer.write('<h3>Completion Performance List</h3>');
1146 if (handler == null) {
1147 return;
1148 }
1149 buffer.write('<table>');
1150 _writeRow(
1151 buffer,
1152 [
1153 'Start Time',
1154 '',
1155 'First (ms)',
1156 '',
1157 'Complete (ms)',
1158 '',
1159 '# Notifications',
1160 '',
1161 '# Suggestions',
1162 '',
1163 'Snippet'
1164 ],
1165 header: true);
1166 int index = 0;
1167 for (CompletionPerformance performance in handler.performanceList) {
1168 String link = makeLink(COMPLETION_PATH, {'index': '$index'},
1169 '${performance.startTimeAndMs}');
1170 _writeRow(buffer, [
1171 link,
1172 '&nbsp;&nbsp;',
1173 performance.firstNotificationInMilliseconds,
1174 '&nbsp;&nbsp;',
1175 performance.elapsedInMilliseconds,
1176 '&nbsp;&nbsp;',
1177 performance.notificationCount,
1178 '&nbsp;&nbsp;',
1179 performance.suggestionCount,
1180 '&nbsp;&nbsp;',
1181 HTML_ESCAPE.convert(performance.snippet)
1182 ]);
1183 ++index;
1184 }
1185
1186 buffer.write('</table>');
1187 buffer.write('''
1188 <p><strong>First (ms)</strong> - the number of milliseconds
1189 from when completion received the request until the first notification
1190 with completion results was queued for sending back to the client.
1191 <p><strong>Complete (ms)</strong> - the number of milliseconds
1192 from when completion received the request until the final notification
1193 with completion results was queued for sending back to the client.
1194 <p><strong># Notifications</strong> - the total number of notifications
1195 sent to the client with completion results for this request.
1196 <p><strong># Suggestions</strong> - the number of suggestions
1197 sent to the client in the first notification, followed by a comma,
1198 followed by the number of suggestions send to the client
1199 in the last notification. If there is only one notification,
1200 then there will be only one number in this column.''');
1201 }
1202
1203 /**
1204 * Generate a table showing the cache values corresponding to the given
1205 * [descriptors], using [getState] to get the cache state corresponding to
1206 * each descriptor, and [getValue] to get the cached value corresponding to
1207 * each descriptor. Append the resulting HTML to the given [buffer]. The
1208 * [linkParameters] will be used if the value is too large to be displayed on
1209 * the current page and needs to be linked to a separate page.
1210 */
1211 void _writeDescriptorTable(
1212 StringBuffer buffer,
1213 List<DataDescriptor> descriptors,
1214 CacheState getState(DataDescriptor),
1215 dynamic getValue(DataDescriptor),
1216 Map<String, String> linkParameters) {
1217 buffer.write('<dl>');
1218 for (DataDescriptor descriptor in descriptors) {
1219 String descriptorName = HTML_ESCAPE.convert(descriptor.toString());
1220 String descriptorState =
1221 HTML_ESCAPE.convert(getState(descriptor).toString());
1222 buffer.write('<dt>$descriptorName ($descriptorState)</dt><dd>');
1223 try {
1224 _writeValueAsHtml(buffer, getValue(descriptor), linkParameters);
1225 } catch (exception) {
1226 buffer.write('(${HTML_ESCAPE.convert(exception.toString())})');
1227 }
1228 buffer.write('</dd>');
1229 }
1230 buffer.write('</dl>');
1231 }
1232
1233 /**
1234 * Write the status of the edit domain (on the main status page) to the given
1235 * [buffer].
1236 */
1237 void _writeEditStatus(StringBuffer buffer) {
1238 buffer.write('<h3>Edit Domain</h3>');
1239 _writeTwoColumns(buffer, (StringBuffer buffer) {
1240 buffer.write('<p><b>Performance Data</b></p>');
1241 buffer.write('<p>');
1242 buffer.write(makeLink(COMPLETION_PATH, {}, 'Completion data'));
1243 buffer.write('</p>');
1244 }, (StringBuffer buffer) {});
1245 }
1246
1247 /**
1248 * Write a representation of the given [caughtException] to the given
1249 * [buffer]. If [isCause] is `true`, then the exception was a cause for
1250 * another exception.
1251 */
1252 void _writeException(StringBuffer buffer, CaughtException caughtException,
1253 {bool isCause: false}) {
1254 Object exception = caughtException.exception;
1255
1256 if (exception is AnalysisException) {
1257 buffer.write('<p>');
1258 if (isCause) {
1259 buffer.write('Caused by ');
1260 }
1261 buffer.write(exception.message);
1262 buffer.write('</p>');
1263 _writeStackTrace(buffer, caughtException.stackTrace);
1264 CaughtException cause = exception.cause;
1265 if (cause != null) {
1266 buffer.write('<blockquote>');
1267 _writeException(buffer, cause, isCause: true);
1268 buffer.write('</blockquote>');
1269 }
1270 } else {
1271 buffer.write('<p>');
1272 if (isCause) {
1273 buffer.write('Caused by ');
1274 }
1275 buffer.write(exception.toString());
1276 buffer.write('<p>');
1277 _writeStackTrace(buffer, caughtException.stackTrace);
1278 }
1279 }
1280
1281 /**
1282 * Write the status of the execution domain (on the main status page) to the
1283 * given [buffer].
1284 */
1285 void _writeExecutionStatus(StringBuffer buffer) {
1286 AnalysisServer analysisServer = _server.analysisServer;
1287 ExecutionDomainHandler handler = analysisServer.handlers.firstWhere(
1288 (RequestHandler handler) => handler is ExecutionDomainHandler,
1289 orElse: () => null);
1290 Set<ExecutionService> services = new Set<ExecutionService>();
1291 if (handler.onFileAnalyzed != null) {
1292 services.add(ExecutionService.LAUNCH_DATA);
1293 }
1294
1295 if (handler != null) {
1296 buffer.write('<h3>Execution Domain</h3>');
1297 _writeTwoColumns(buffer, (StringBuffer buffer) {
1298 _writeSubscriptionList(buffer, ExecutionService.VALUES, services);
1299 }, (StringBuffer buffer) {});
1300 }
1301 }
1302
1303 /**
1304 * Write a representation of an analysis option with the given [name] and
1305 * [value] to the given [buffer]. The option should be separated from other
1306 * options unless the [last] flag is true, indicating that this is the last
1307 * option in the list of options.
1308 */
1309 void _writeOption(StringBuffer buffer, String name, Object value,
1310 {bool last: false}) {
1311 buffer.write(name);
1312 buffer.write(' = ');
1313 buffer.write(value.toString());
1314 if (!last) {
1315 buffer.write('<br>');
1316 }
1317 }
1318
1319 /**
1320 * Write a standard HTML page to the given [buffer]. The page will have the
1321 * given [title] and a body that is generated by the given [body] generator.
1322 */
1323 void _writePage(StringBuffer buffer, String title, List<String> subtitles,
1324 HtmlGenerator body) {
1325 DateTime now = new DateTime.now();
1326 String date = "${now.month}/${now.day}/${now.year}";
1327 String time =
1328 "${now.hour}:${_twoDigit(now.minute)}:${_twoDigit(now.second)}.${now.mil lisecond}";
1329
1330 buffer.write('<!DOCTYPE html>');
1331 buffer.write('<html>');
1332 buffer.write('<head>');
1333 buffer.write('<meta charset="utf-8">');
1334 buffer.write(
1335 '<meta name="viewport" content="width=device-width, initial-scale=1.0">' );
1336 buffer.write('<title>$title</title>');
1337 buffer.write('<style>');
1338 buffer.write('a {color: #0000DD; text-decoration: none;}');
1339 buffer.write('a:link.error {background-color: #FFEEEE;}');
1340 buffer.write('a:visited.error {background-color: #FFEEEE;}');
1341 buffer.write('a:hover.error {background-color: #FFEEEE;}');
1342 buffer.write('a:active.error {background-color: #FFEEEE;}');
1343 buffer.write(
1344 'h3 {background-color: #DDDDDD; margin-top: 0em; margin-bottom: 0em;}');
1345 buffer.write('p {margin-top: 0.5em; margin-bottom: 0.5em;}');
1346 // response.write('span.error {text-decoration-line: underline; text-decorati on-color: red; text-decoration-style: wavy;}');
1347 buffer.write(
1348 'table.column {border: 0px solid black; width: 100%; table-layout: fixed ;}');
1349 buffer.write('td.column {vertical-align: top; width: 50%;}');
1350 buffer.write('td.right {text-align: right;}');
1351 buffer.write('</style>');
1352 buffer.write('</head>');
1353
1354 buffer.write('<body>');
1355 buffer.write(
1356 '<h2>$title <small><small>(as of $time on $date)</small></small></h2>');
1357 if (subtitles != null && subtitles.isNotEmpty) {
1358 buffer.write('<blockquote>');
1359 bool first = true;
1360 for (String subtitle in subtitles) {
1361 if (first) {
1362 first = false;
1363 } else {
1364 buffer.write('<br>');
1365 }
1366 buffer.write('<b>');
1367 buffer.write(subtitle);
1368 buffer.write('</b>');
1369 }
1370 buffer.write('</blockquote>');
1371 }
1372 try {
1373 body(buffer);
1374 } catch (exception, stackTrace) {
1375 buffer.write('<h3>Exception while creating page</h3>');
1376 _writeException(buffer, new CaughtException(exception, stackTrace));
1377 }
1378 buffer.write('</body>');
1379 buffer.write('</html>');
1380 }
1381
1382 /**
1383 * Write the recent output section (on the main status page) to the given
1384 * [buffer] object.
1385 */
1386 void _writePluginStatus(StringBuffer buffer) {
1387 void writePlugin(Plugin plugin) {
1388 buffer.write(plugin.uniqueIdentifier);
1389 buffer.write(' (');
1390 buffer.write(plugin.runtimeType);
1391 buffer.write(')<br>');
1392 }
1393 buffer.write('<h3>Plugin Status</h3><p>');
1394 writePlugin(AnalysisEngine.instance.enginePlugin);
1395 writePlugin(_server.serverPlugin);
1396 for (Plugin plugin in _server.analysisServer.userDefinedPlugins) {
1397 writePlugin(plugin);
1398 }
1399 buffer.write('<p>');
1400 }
1401
1402 /**
1403 * Write the recent output section (on the main status page) to the given
1404 * [buffer] object.
1405 */
1406 void _writeRecentOutput(StringBuffer buffer) {
1407 buffer.write('<h3>Recent Output</h3>');
1408 String output = HTML_ESCAPE.convert(_printBuffer.join('\n'));
1409 if (output.isEmpty) {
1410 buffer.write('<i>none</i>');
1411 } else {
1412 buffer.write('<pre>');
1413 buffer.write(output);
1414 buffer.write('</pre>');
1415 }
1416 }
1417
1418 void _writeResponse(HttpRequest request, HtmlGenerator writePage) {
1419 HttpResponse response = request.response;
1420 response.statusCode = HttpStatus.OK;
1421 response.headers.contentType = _htmlContent;
1422 try {
1423 StringBuffer buffer = new StringBuffer();
1424 try {
1425 writePage(buffer);
1426 } catch (exception, stackTrace) {
1427 buffer.clear();
1428 _writePage(buffer, 'Internal Exception', [], (StringBuffer buffer) {
1429 _writeException(buffer, new CaughtException(exception, stackTrace));
1430 });
1431 }
1432 response.write(buffer.toString());
1433 } finally {
1434 response.close();
1435 }
1436 }
1437
1438 /**
1439 * Write a single row within a table to the given [buffer]. The row will have
1440 * one cell for each of the [columns], and will be a header row if [header] is
1441 * `true`.
1442 */
1443 void _writeRow(StringBuffer buffer, List<Object> columns,
1444 {bool header: false, List<String> classes}) {
1445 buffer.write('<tr>');
1446 int count = columns.length;
1447 int maxClassIndex = classes == null ? 0 : classes.length - 1;
1448 for (int i = 0; i < count; i++) {
1449 String classAttribute = '';
1450 if (classes != null) {
1451 String className = classes[min(i, maxClassIndex)];
1452 if (className != null) {
1453 classAttribute = ' class="$className"';
1454 }
1455 }
1456 if (header) {
1457 buffer.write('<th$classAttribute>');
1458 } else {
1459 buffer.write('<td$classAttribute>');
1460 }
1461 buffer.write(columns[i]);
1462 if (header) {
1463 buffer.write('</th>');
1464 } else {
1465 buffer.write('</td>');
1466 }
1467 }
1468 buffer.write('</tr>');
1469 }
1470
1471 /**
1472 * Write the status of the service domain (on the main status page) to the
1473 * given [response] object.
1474 */
1475 bool _writeServerStatus(StringBuffer buffer) {
1476 AnalysisServer analysisServer = _server.analysisServer;
1477 Set<ServerService> services = analysisServer.serverServices;
1478
1479 buffer.write('<h3>Server Domain</h3>');
1480 _writeTwoColumns(buffer, (StringBuffer buffer) {
1481 if (analysisServer == null) {
1482 buffer.write('Status: <span style="color:red">Not running</span>');
1483 return false;
1484 }
1485 buffer.write('<p>');
1486 buffer.write('Status: Running<br>');
1487 buffer.write('Instrumentation: ');
1488 if (AnalysisEngine.instance.instrumentationService.isActive) {
1489 buffer.write('<span style="color:red">Active</span>');
1490 } else {
1491 buffer.write('Inactive');
1492 }
1493 buffer.write('<br>');
1494 buffer.write('Version: ');
1495 buffer.write(AnalysisServer.VERSION);
1496 buffer.write('</p>');
1497
1498 buffer.write('<p><b>Performance Data</b></p>');
1499 buffer.write('<p>');
1500 buffer.write(makeLink(
1501 COMMUNICATION_PERFORMANCE_PATH, {}, 'Communication performance'));
1502 buffer.write('</p>');
1503 }, (StringBuffer buffer) {
1504 _writeSubscriptionList(buffer, ServerService.VALUES, services);
1505 });
1506 return true;
1507 }
1508
1509 /**
1510 * Write a representation of the given [stackTrace] to the given [buffer].
1511 */
1512 void _writeStackTrace(StringBuffer buffer, StackTrace stackTrace) {
1513 if (stackTrace != null) {
1514 String trace = stackTrace.toString().replaceAll('#', '<br>#');
1515 if (trace.startsWith('<br>#')) {
1516 trace = trace.substring(4);
1517 }
1518 buffer.write('<p>');
1519 buffer.write(trace);
1520 buffer.write('</p>');
1521 }
1522 }
1523
1524 /**
1525 * Given a [service] that could be subscribed to and a set of the services
1526 * that are actually subscribed to ([subscribedServices]), write a
1527 * representation of the service to the given [buffer].
1528 */
1529 void _writeSubscriptionInList(
1530 StringBuffer buffer, Enum service, Set<Enum> subscribedServices) {
1531 if (subscribedServices.contains(service)) {
1532 buffer.write('<code>+ </code>');
1533 } else {
1534 buffer.write('<code>- </code>');
1535 }
1536 buffer.write(service.name);
1537 buffer.write('<br>');
1538 }
1539
1540 /**
1541 * Given a [service] that could be subscribed to and a set of paths that are
1542 * subscribed to the services ([subscribedPaths]), write a representation of
1543 * the service to the given [buffer].
1544 */
1545 void _writeSubscriptionInMap(
1546 StringBuffer buffer, Enum service, Set<String> subscribedPaths) {
1547 buffer.write('<p>');
1548 buffer.write(service.name);
1549 buffer.write('</p>');
1550 if (subscribedPaths == null || subscribedPaths.isEmpty) {
1551 buffer.write('none');
1552 } else {
1553 List<String> paths = subscribedPaths.toList();
1554 paths.sort();
1555 for (String path in paths) {
1556 buffer.write('<p>');
1557 buffer.write(path);
1558 buffer.write('</p>');
1559 }
1560 }
1561 }
1562
1563 /**
1564 * Given a list containing all of the services that can be subscribed to in a
1565 * single domain ([allServices]) and a set of the services that are actually
1566 * subscribed to ([subscribedServices]), write a representation of the
1567 * subscriptions to the given [buffer].
1568 */
1569 void _writeSubscriptionList(StringBuffer buffer, List<Enum> allServices,
1570 Set<Enum> subscribedServices) {
1571 buffer.write('<p><b>Subscriptions</b></p>');
1572 buffer.write('<p>');
1573 for (Enum service in allServices) {
1574 _writeSubscriptionInList(buffer, service, subscribedServices);
1575 }
1576 buffer.write('</p>');
1577 }
1578
1579 /**
1580 * Given a list containing all of the services that can be subscribed to in a
1581 * single domain ([allServices]) and a set of the services that are actually
1582 * subscribed to ([subscribedServices]), write a representation of the
1583 * subscriptions to the given [buffer].
1584 */
1585 void _writeSubscriptionMap(StringBuffer buffer, List<Enum> allServices,
1586 Map<Enum, Set<String>> subscribedServices) {
1587 buffer.write('<p><b>Subscriptions</b></p>');
1588 for (Enum service in allServices) {
1589 _writeSubscriptionInMap(buffer, service, subscribedServices[service]);
1590 }
1591 }
1592
1593 /**
1594 * Write two columns of information to the given [buffer], where the
1595 * [leftColumn] and [rightColumn] functions are used to generate the content
1596 * of those columns.
1597 */
1598 void _writeTwoColumns(StringBuffer buffer, HtmlGenerator leftColumn,
1599 HtmlGenerator rightColumn) {
1600 buffer
1601 .write('<table class="column"><tr class="column"><td class="column">');
1602 leftColumn(buffer);
1603 buffer.write('</td><td class="column">');
1604 rightColumn(buffer);
1605 buffer.write('</td></tr></table>');
1606 }
1607
1608 /**
1609 * Render the given [value] as HTML and append it to the given [buffer]. The
1610 * [linkParameters] will be used if the value is too large to be displayed on
1611 * the current page and needs to be linked to a separate page.
1612 */
1613 void _writeValueAsHtml(
1614 StringBuffer buffer, Object value, Map<String, String> linkParameters) {
1615 if (value == null) {
1616 buffer.write('<i>null</i>');
1617 } else if (value is String) {
1618 buffer.write('<pre>${HTML_ESCAPE.convert(value)}</pre>');
1619 } else if (value is List) {
1620 buffer.write('List containing ${value.length} entries');
1621 buffer.write('<ul>');
1622 for (var entry in value) {
1623 buffer.write('<li>');
1624 _writeValueAsHtml(buffer, entry, linkParameters);
1625 buffer.write('</li>');
1626 }
1627 buffer.write('</ul>');
1628 } else if (value is AstNode) {
1629 String link =
1630 makeLink(AST_PATH, linkParameters, value.runtimeType.toString());
1631 buffer.write('<i>$link</i>');
1632 } else if (value is Element) {
1633 String link =
1634 makeLink(ELEMENT_PATH, linkParameters, value.runtimeType.toString());
1635 buffer.write('<i>$link</i>');
1636 } else {
1637 buffer.write(HTML_ESCAPE.convert(value.toString()));
1638 buffer.write(' <i>(${value.runtimeType.toString()})</i>');
1639 }
1640 }
1641
1642 /**
1643 * Create a link to [path] with query parameters [params], with inner HTML
1644 * [innerHtml]. If [hasError] is `true`, then the link will have the class
1645 * 'error'.
1646 */
1647 static String makeLink(
1648 String path, Map<String, String> params, String innerHtml,
1649 [bool hasError = false]) {
1650 Uri uri = new Uri(path: path, queryParameters: params);
1651 String href = HTML_ESCAPE.convert(uri.toString());
1652 String classAttribute = hasError ? ' class="error"' : '';
1653 return '<a href="$href"$classAttribute>$innerHtml</a>';
1654 }
1655 }
OLDNEW
« no previous file with comments | « pkg/analysis_server/lib/src/domain_diagnostic.dart ('k') | pkg/analysis_server/lib/src/server/driver.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698