OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2017, 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 | |
8 import 'package:analysis_server/protocol/protocol_generated.dart'; | |
9 import 'package:analysis_server/src/analysis_server.dart'; | |
10 import 'package:analysis_server/src/domain_completion.dart'; | |
11 import 'package:analysis_server/src/domain_execution.dart'; | |
12 import 'package:analysis_server/src/plugin/plugin_manager.dart'; | |
13 import 'package:analysis_server/src/server/http_server.dart'; | |
14 import 'package:analysis_server/src/services/completion/completion_performance.d art'; | |
15 import 'package:analysis_server/src/socket_server.dart'; | |
16 import 'package:analysis_server/src/status/pages.dart'; | |
17 import 'package:analyzer/exception/exception.dart'; | |
18 import 'package:analyzer/file_system/file_system.dart'; | |
19 import 'package:analyzer/instrumentation/instrumentation.dart'; | |
20 import 'package:analyzer/source/package_map_resolver.dart'; | |
21 import 'package:analyzer/source/sdk_ext.dart'; | |
22 import 'package:analyzer/src/context/source.dart'; | |
23 import 'package:analyzer/src/dart/analysis/driver.dart'; | |
24 import 'package:analyzer/src/dart/analysis/file_state.dart'; | |
25 import 'package:analyzer/src/dart/sdk/sdk.dart'; | |
26 import 'package:analyzer/src/generated/engine.dart'; | |
27 import 'package:analyzer/src/generated/sdk.dart'; | |
28 import 'package:analyzer/src/generated/source.dart'; | |
29 import 'package:analyzer/src/generated/utilities_general.dart'; | |
30 import 'package:plugin/plugin.dart'; | |
31 | |
32 final String kCustomCss = ''' | |
33 .lead, .page-title+.markdown-body>p:first-child { | |
34 margin-bottom: 30px; | |
35 font-size: 20px; | |
36 font-weight: 300; | |
37 color: #555; | |
38 } | |
39 | |
40 .container { | |
41 width: 1160px; | |
42 } | |
43 | |
44 .masthead { | |
45 padding-top: 1rem; | |
46 padding-bottom: 1rem; | |
47 margin-bottom: 1.5rem; | |
48 text-align: center; | |
49 background-color: #4078c0; | |
50 } | |
51 | |
52 .masthead .masthead-logo { | |
53 display: inline-block; | |
54 font-size: 1.5rem; | |
55 color: #fff; | |
56 float: left; | |
57 } | |
58 | |
59 .masthead-nav { | |
60 float: right; | |
61 margin-top: .5rem; | |
62 } | |
63 | |
64 .masthead-nav a:not(:last-child) { | |
65 margin-right: 1.25rem; | |
66 } | |
67 | |
68 .masthead a { | |
69 color: rgba(255,255,255,0.5); | |
70 font-size: 1rem; | |
71 } | |
72 | |
73 .masthead a:hover { | |
74 color: #fff; | |
75 text-decoration: none; | |
76 } | |
77 | |
78 .masthead-nav .active { | |
79 color: #fff; | |
80 font-weight: 500; | |
81 } | |
82 | |
83 .counter { | |
84 display: inline-block; | |
85 padding: 2px 5px; | |
86 font-size: 11px; | |
87 font-weight: bold; | |
88 line-height: 1; | |
89 color: #666; | |
90 background-color: #eee; | |
91 border-radius: 20px; | |
92 } | |
93 | |
94 .menu-item .counter { | |
95 float: right; | |
96 margin-left: 5px; | |
97 } | |
98 | |
99 td.right { | |
100 text-align: right; | |
101 } | |
102 | |
103 td.pre { | |
104 white-space: pre; | |
105 } | |
106 | |
107 .nowrap { | |
108 white-space: nowrap; | |
109 } | |
110 | |
111 .footer { | |
112 padding-top: 3rem; | |
113 padding-bottom: 3rem; | |
114 margin-top: 3rem; | |
115 line-height: 1.75; | |
116 color: #7a7a7a; | |
117 border-top: 1px solid #eee; | |
118 } | |
119 | |
120 .footer strong { | |
121 color: #333; | |
122 } | |
123 '''; | |
124 | |
125 class DiagnosticsSite extends Site implements AbstractGetHandler { | |
126 /// An object that can handle either a WebSocket connection or a connection | |
127 /// to the client over stdio. | |
128 SocketServer socketServer; | |
129 | |
130 /// The last few lines printed. | |
131 List<String> lastPrintedLines = <String>[]; | |
132 | |
133 DiagnosticsSite(this.socketServer, this.lastPrintedLines) | |
134 : super('Analysis Server') { | |
135 pages.add(new CompletionPage(this)); | |
136 pages.add(new CommunicationsPage(this)); | |
137 pages.add(new ContextsPage(this)); | |
138 pages.add(new ExecutionDomainPage(this)); | |
139 pages.add(new OverlaysPage(this)); | |
140 pages.add(new ProfilePage(this)); | |
141 //pages.add(new ExceptionsPage(this)); | |
142 pages.add(new InstrumentationPage(this)); | |
143 pages.add(new PluginsPage(this)); | |
144 | |
145 pages.sort(((Page a, Page b) => | |
146 a.title.toLowerCase().compareTo(a.title.toLowerCase()))); | |
Brian Wilkerson
2017/05/30 21:09:19
One of these 'a's should be a 'b'.
devoncarew
2017/05/30 23:31:31
oops
| |
147 | |
148 // Add the status page at the beginning. | |
149 pages.insert(0, new StatusPage(this)); | |
150 | |
151 // Add non-nav pages. | |
152 pages.add(new FeedbackPage(this)); | |
153 } | |
154 | |
155 String get customCss => kCustomCss; | |
156 | |
157 Page createUnknownPage(String unknownPath) => | |
158 new NotFoundPage(this, unknownPath); | |
159 | |
160 Page createExceptionPage(String message, StackTrace trace) => | |
161 new ExceptionPage(this, message, trace); | |
162 } | |
163 | |
164 /// A page with a proscriptive notion of layout. | |
165 abstract class DiagnosticPage extends Page { | |
166 final Site site; | |
167 | |
168 DiagnosticPage(this.site, String id, String title, {String description}) | |
169 : super(id, title, description: description); | |
170 | |
171 AnalysisServer get server => | |
172 (site as DiagnosticsSite).socketServer.analysisServer; | |
173 | |
174 void generatePage(Map<String, String> params) { | |
175 buf.writeln('<!DOCTYPE html><html lang="en">'); | |
176 buf.write('<head>'); | |
177 buf.write('<meta charset="utf-8">'); | |
178 buf.write('<meta name="viewport" content="width=device-width, ' | |
179 'initial-scale=1.0">'); | |
180 buf.writeln('<title>${site.title}</title>'); | |
181 buf.writeln('<link rel="stylesheet" ' | |
182 'href="https://cdnjs.cloudflare.com/ajax/libs/Primer/6.0.0/build.css">') ; | |
183 buf.writeln('<link rel="stylesheet" ' | |
184 'href="https://cdnjs.cloudflare.com/ajax/libs/octicons/4.4.0/font/octico ns.css">'); | |
185 buf.writeln('<script type="text/javascript" ' | |
186 'src="https://www.gstatic.com/charts/loader.js"></script>'); | |
187 buf.writeln('<style>${site.customCss}</style>'); | |
188 buf.writeln('</head>'); | |
189 | |
190 buf.writeln('<body>'); | |
191 generateHeader(); | |
192 buf.writeln('<div class="container">'); | |
193 generateContainer(params); | |
194 generateFooter(); | |
195 buf.writeln('</div>'); // div.container | |
196 buf.writeln('</body>'); | |
197 buf.writeln('</html>'); | |
198 } | |
199 | |
200 void generateHeader() { | |
201 buf.writeln(''' | |
202 <header class="masthead"> | |
203 <div class="container"> | |
204 <span class="masthead-logo"> | |
205 <span class="mega-octicon octicon-database"></span> | |
206 ${site.title} Diagnostics | |
207 </span> | |
208 | |
209 <nav class="masthead-nav"> | |
210 <a href="/status" ${isNavPage ? ' class="active"' : ''}>Diagnostics</a> | |
211 <a href="/feedback" ${isCurrentPage('/feedback') ? ' class="active"' : ' '}>Feedback</a> | |
212 <a href="https://www.dartlang.org/tools/analyzer" target="_blank">Docs</ a> | |
213 <a href="https://htmlpreview.github.io/?https://github.com/dart-lang/sdk /blob/master/pkg/analysis_server/doc/api.html" target="_blank">Spec</a> | |
214 </nav> | |
215 </div> | |
216 </header> | |
217 '''); | |
218 } | |
219 | |
220 void generateContainer(Map<String, String> params) { | |
221 buf.writeln('<div class="columns docs-layout">'); | |
222 buf.writeln('<div class="three-fourths column markdown-body">'); | |
223 h1(title, classes: 'page-title'); | |
224 div(() { | |
225 p(description); | |
226 generateContent(params); | |
227 }, classes: 'markdown-body'); | |
228 buf.writeln('</div>'); | |
229 buf.writeln('</div>'); | |
230 } | |
231 | |
232 void generateContent(Map<String, String> params); | |
233 | |
234 void generateFooter() { | |
235 buf.writeln(''' | |
236 <footer class="footer"> | |
237 Dart ${site.title} <span style="float:right">SDK ${_sdkVersion}</span> | |
238 </footer> | |
239 '''); | |
240 } | |
241 | |
242 bool get isNavPage => false; | |
243 } | |
244 | |
245 abstract class DiagnosticPageWithNav extends DiagnosticPage { | |
246 DiagnosticPageWithNav(Site site, String id, String title, | |
247 {String description}) | |
248 : super(site, id, title, description: description); | |
249 | |
250 void generateContainer(Map<String, String> params) { | |
251 buf.writeln('<div class="columns docs-layout">'); | |
252 | |
253 buf.writeln('<div class="one-fifth column">'); | |
254 buf.writeln('<nav class="menu docs-menu">'); | |
255 for (Page page in site.pages.where((p) => p is DiagnosticPageWithNav)) { | |
256 buf.write('<a class="menu-item ${page == this ? ' selected' : ''}" ' | |
257 'href="${page.path}">${escape(page.title)}'); | |
258 String detail = (page as DiagnosticPageWithNav).navDetail; | |
259 if (detail != null) buf.write('<span class="counter">$detail</span>'); | |
Brian Wilkerson
2017/05/30 21:09:19
style nit: We have a convention (within the analyz
devoncarew
2017/05/30 23:31:31
Done.
| |
260 buf.writeln('</a>'); | |
261 } | |
262 buf.writeln('</nav>'); | |
263 buf.writeln('</div>'); // div.one-fourth | |
Brian Wilkerson
2017/05/30 21:09:19
"one-fifth"? Consider dropping comments that can q
devoncarew
2017/05/30 23:31:31
Done.
| |
264 | |
265 buf.writeln('<div class="four-fifths column markdown-body">'); | |
266 h1(title, classes: 'page-title'); | |
267 div(() { | |
268 p(description); | |
269 generateContent(params); | |
270 }, classes: 'markdown-body'); | |
271 buf.writeln('</div>'); // div.three-fourths | |
272 | |
273 buf.writeln('</div>'); // div.columns | |
274 } | |
275 | |
276 String get navDetail => null; | |
277 | |
278 bool get isNavPage => true; | |
279 } | |
280 | |
281 class NotFoundPage extends DiagnosticPage { | |
282 final String path; | |
283 | |
284 NotFoundPage(Site site, this.path) | |
285 : super(site, '', '404 Not found', description: "'$path' not found."); | |
286 | |
287 void generateContent(Map<String, String> params) {} | |
288 } | |
289 | |
290 class ExceptionPage extends DiagnosticPage { | |
291 final StackTrace trace; | |
292 | |
293 ExceptionPage(Site site, String message, this.trace) | |
294 : super(site, '', '500 Oops', description: message); | |
295 | |
296 void generateContent(Map<String, String> params) { | |
297 p(trace.toString(), style: 'white-space: pre'); | |
298 } | |
299 } | |
300 | |
301 class FeedbackPage extends DiagnosticPage { | |
302 FeedbackPage(DiagnosticsSite site) | |
303 : super(site, 'feedback', 'Feedback', | |
304 description: 'Providing feedback and filing issues.'); | |
305 | |
306 @override | |
307 void generateContent(Map<String, String> params) { | |
308 final String issuesUrl = 'https://github.com/dart-lang/sdk/issues'; | |
309 p( | |
310 'To file issues or feature requests, see our ' | |
311 '<a href="$issuesUrl">bug tracker</a>. When filing an issue, please de scribe:', | |
312 raw: true, | |
313 ); | |
314 ul([ | |
315 'what you were doing', | |
316 'what occured', | |
317 'what you think the expected behavior should have been', | |
318 ], (line) => buf.writeln(line)); | |
319 | |
320 p('Other data to include:'); | |
321 ul([ | |
322 "the IDE you are using and it's version", | |
323 'the Dart SDK version (<code>${escape(_sdkVersion)}</code>', | |
324 'your operating system (<code>${escape(Platform.operatingSystem)}</code>)' , | |
325 ], (line) => buf.writeln(line)); | |
326 | |
327 p('Thanks!'); | |
328 } | |
329 } | |
330 | |
331 class StatusPage extends DiagnosticPageWithNav { | |
332 StatusPage(DiagnosticsSite site) | |
333 : super(site, 'status', 'Status', | |
334 description: | |
335 'General status and diagnostics for the analysis server.'); | |
336 | |
337 @override | |
338 void generateContent(Map<String, String> params) { | |
339 buf.writeln('<div class="columns">'); | |
340 | |
341 buf.writeln('<div class="column one-half">'); | |
342 h3('Status'); | |
343 buf.writeln(writeOption( | |
344 'New analysis driver enabled', server.options.enableNewAnalysisDriver)); | |
345 buf.writeln(writeOption('Instrumentation enabled', | |
346 AnalysisEngine.instance.instrumentationService.isActive)); | |
347 buf.writeln(writeOption('Server process ID', pid)); | |
348 buf.writeln('</div>'); | |
349 | |
350 buf.writeln('<div class="column one-half">'); | |
351 h3('Versions'); | |
352 buf.writeln(writeOption('Analysis server version', AnalysisServer.VERSION)); | |
353 buf.writeln(writeOption('Dart SDK', Platform.version)); | |
354 buf.writeln('</div>'); | |
355 | |
356 buf.writeln('</div>'); | |
357 | |
358 h3('Server domain subscriptions'); | |
359 ul(ServerService.VALUES, (item) { | |
360 if (server.serverServices.contains(item)) { | |
361 buf.write('$item (has subscriptions)'); | |
362 } else { | |
363 buf.write('$item (no subscriptions)'); | |
364 } | |
365 }); | |
366 | |
367 h3('Analysis domain subscriptions'); | |
368 for (AnalysisService service in AnalysisService.VALUES) { | |
369 buf.writeln('${service.name}<br>'); | |
370 ul(server.analysisServices[service] ?? [], (item) { | |
371 buf.write('$item'); | |
372 }); | |
373 } | |
374 | |
375 List<String> lines = (site as DiagnosticsSite).lastPrintedLines; | |
376 if (lines.isNotEmpty) { | |
377 h3('Debug output'); | |
378 p(lines.join('\n'), style: 'white-space: pre'); | |
379 } | |
380 } | |
381 } | |
382 | |
383 class InstrumentationPage extends DiagnosticPageWithNav { | |
384 InstrumentationPage(DiagnosticsSite site) | |
385 : super(site, 'instrumentation', 'Instrumentation', | |
386 description: | |
387 'Verbose instrumentation data from the analysis server.'); | |
388 | |
389 @override | |
390 void generateContent(Map<String, String> params) { | |
391 p( | |
392 'Instrumentation can be enabled by starting the analysis server with the ' | |
393 '<code>--instrumentation-log-file=path/to/file</code> flag.', | |
394 raw: true); | |
395 | |
396 if (!AnalysisEngine.instance.instrumentationService.isActive) { | |
397 blankslate('Instrumentation not active.'); | |
398 return; | |
399 } | |
400 | |
401 h3('Instrumentation'); | |
402 | |
403 p('Instrumentation active.'); | |
404 | |
405 InstrumentationServer instrumentation = | |
406 AnalysisEngine.instance.instrumentationService.instrumentationServer; | |
407 String description = instrumentation.describe; | |
408 HtmlEscape htmlEscape = new HtmlEscape(HtmlEscapeMode.ELEMENT); | |
409 description = htmlEscape.convert(description); | |
410 // Convert http(s): references to hyperlinks. | |
411 final RegExp urlRegExp = new RegExp(r'[http|https]+:\/*(\S+)'); | |
412 description = description.replaceAllMapped(urlRegExp, (Match match) { | |
413 return '<a href="${match.group(0)}">${match.group(1)}</a>'; | |
414 }); | |
415 p(description.replaceAll('\n', '<br>'), raw: true); | |
416 } | |
417 } | |
418 | |
419 class ProfilePage extends DiagnosticPageWithNav { | |
420 ProfilePage(DiagnosticsSite site) | |
421 : super(site, 'profile', 'Profiling Info', | |
422 description: 'Profiling performance tag data.'); | |
423 | |
424 @override | |
425 void generateContent(Map<String, String> params) { | |
426 // prepare sorted tags | |
427 List<PerformanceTag> tags = PerformanceTag.all.toList(); | |
428 tags.remove(ServerPerformanceStatistics.idle); | |
429 tags.removeWhere((tag) => tag.label == 'unknown'); | |
430 tags.sort((a, b) => b.elapsedMs - a.elapsedMs); | |
431 | |
432 // draw a pie chart | |
433 String rowData = | |
434 tags.map((tag) => "['${tag.label}', ${tag.elapsedMs}]").join(','); | |
435 buf.writeln( | |
436 '<div id="chart-div" style="width: 700px; height: 300px;"></div>'); | |
437 buf.writeln(''' | |
438 <script type="text/javascript"> | |
439 google.charts.load('current', {'packages':['corechart']}); | |
440 google.charts.setOnLoadCallback(drawChart); | |
441 | |
442 function drawChart() { | |
443 var data = new google.visualization.DataTable(); | |
444 data.addColumn('string', 'Tag'); | |
445 data.addColumn('number', 'Time (ms)'); | |
446 data.addRows([$rowData]); | |
447 var options = {'title': 'Performance Tag Data', 'width': 700, 'height' : 300}; | |
448 var chart = new google.visualization.PieChart(document.getElementById( 'chart-div')); | |
449 chart.draw(data, options); | |
450 } | |
451 </script> | |
452 '''); | |
453 | |
454 // print total time | |
455 int totalTime = | |
456 tags.fold<int>(0, (int a, PerformanceTag tag) => a + tag.elapsedMs); | |
457 p('Total measured time: ${printMilliseconds(totalTime)}'); | |
458 | |
459 // write out a table | |
460 void _writeRow(List<String> data, {bool header: false}) { | |
461 buf.write('<tr>'); | |
462 if (header) { | |
463 for (String d in data) { | |
464 buf.write('<th>$d</th>'); | |
465 } | |
466 } else { | |
467 for (String d in data) { | |
468 buf.write('<td>$d</td>'); | |
469 } | |
470 } | |
471 buf.writeln('</tr>'); | |
472 } | |
473 | |
474 buf.write('<table>'); | |
475 _writeRow(['Tag name', 'Time (in ms)', 'Percent'], header: true); | |
476 void writeRow(PerformanceTag tag) { | |
477 double percent = tag.elapsedMs / totalTime; | |
478 _writeRow([ | |
479 tag.label, | |
480 printMilliseconds(tag.elapsedMs), | |
481 printPercentage(percent) | |
482 ]); | |
483 } | |
484 | |
485 tags.forEach(writeRow); | |
486 buf.write('</table>'); | |
487 } | |
488 } | |
489 | |
490 // TODO(devoncarew): This is not hooked up. | |
491 class ExceptionsPage extends DiagnosticPageWithNav { | |
492 ExceptionsPage(DiagnosticsSite site) | |
493 : super(site, 'exceptions', 'Exceptions', | |
494 description: 'Exceptions from the analysis server.'); | |
495 | |
496 String get navDetail => exceptions.length.toString(); | |
497 | |
498 @override | |
499 void generateContent(Map<String, String> params) { | |
500 if (exceptions.isEmpty) { | |
501 blankslate('No exceptions encountered!'); | |
502 } else { | |
503 for (CaughtException ex in exceptions) { | |
504 h3('${ex.exception}'); | |
505 p(ex.toString(), style: 'white-space: pre'); | |
506 } | |
507 } | |
508 } | |
509 | |
510 // TODO: Implement - read this from a server exception ring buffer. | |
511 List<CaughtException> get exceptions => []; | |
512 } | |
513 | |
514 class ContextsPage extends DiagnosticPageWithNav { | |
515 ContextsPage(DiagnosticsSite site) | |
516 : super(site, 'contexts', 'Contexts', | |
517 description: | |
518 'An analysis context defines the options and the set of sources being analyzed.'); | |
519 | |
520 String get navDetail => printInteger(server.driverMap.length); | |
521 | |
522 @override | |
523 void generateContent(Map<String, String> params) { | |
524 Map<Folder, AnalysisDriver> driverMap = server.driverMap; | |
525 if (driverMap.isEmpty) { | |
526 blankslate('No contexts.'); | |
527 return; | |
528 } | |
529 | |
530 String contextPath = params['context']; | |
531 Folder folder = driverMap.keys | |
532 .firstWhere((f) => f.path == contextPath, orElse: () => null); | |
533 | |
534 if (folder == null) { | |
535 folder = driverMap.keys.first; | |
536 contextPath = folder.path; | |
537 } | |
538 | |
539 AnalysisDriver driver = driverMap[folder]; | |
540 | |
541 buf.writeln('<div class="tabnav">'); | |
542 buf.writeln('<nav class="tabnav-tabs">'); | |
543 for (Folder f in driverMap.keys) { | |
544 if (f == folder) { | |
545 buf.writeln( | |
546 '<a class="tabnav-tab selected">${escape(f.shortName)}</a>'); | |
547 } else { | |
548 String p = '$path?context=${Uri.encodeQueryComponent(f.path)}'; | |
549 buf.writeln( | |
550 '<a href="$p" class="tabnav-tab">${escape(f.shortName)}</a>'); | |
551 } | |
552 } | |
553 buf.writeln('</nav>'); | |
554 buf.writeln('</div>'); | |
555 | |
556 p('Context path is $contextPath.'); | |
557 | |
558 buf.writeln( | |
559 writeOption('Has .packages file', folder.getChild('.packages').exists)); | |
560 buf.writeln(writeOption( | |
561 'Has pubspec.yaml file', folder.getChild('pubspec.yaml').exists)); | |
562 buf.writeln(writeOption('Analysis options path', | |
563 escape(driver.contextRoot.optionsFilePath) ?? 'none')); | |
564 | |
565 buf.writeln('<div class="columns">'); | |
566 | |
567 buf.writeln('<div class="column one-half">'); | |
568 h3('Analysis options'); | |
569 p(describe(driver.analysisOptions), raw: true); | |
570 buf.writeln('</div>'); | |
571 | |
572 buf.writeln('<div class="column one-half">'); | |
573 DartSdk sdk = driver?.sourceFactory?.dartSdk; | |
574 AnalysisOptionsImpl sdkOptions = sdk?.context?.analysisOptions; | |
575 if (sdkOptions != null) { | |
576 h3('SDK analysis options'); | |
577 p(describe(sdkOptions), raw: true); | |
578 | |
579 if (sdk is FolderBasedDartSdk) { | |
580 p(writeOption('Use summaries', sdk.useSummary), raw: true); | |
581 } | |
582 } | |
583 buf.writeln('</div>'); | |
584 | |
585 buf.writeln('</div>'); | |
586 | |
587 h3('Lints'); | |
588 p(driver.analysisOptions.lintRules.map((l) => l.name).join(', ')); | |
589 | |
590 h3('Error processors'); | |
591 p(driver.analysisOptions.errorProcessors | |
592 .map((e) => e.description) | |
593 .join(', ')); | |
594 | |
595 List<String> priorityFiles = driver.priorityFiles; | |
596 List<String> addedFiles = driver.addedFiles.toList(); | |
597 List<String> implicitFiles = | |
598 driver.knownFiles.difference(driver.addedFiles).toList(); | |
599 addedFiles.sort(); | |
600 implicitFiles.sort(); | |
601 | |
602 String lenCounter(List list) { | |
603 return '<span class="counter" style="float: right;">${list.length}</span>' ; | |
604 } | |
605 | |
606 h3('Context files'); | |
607 | |
608 h4('Priority files ${lenCounter(priorityFiles)}', raw: true); | |
609 inputList(priorityFiles, (file) => buf.write(file)); | |
610 | |
611 h4('Added files ${lenCounter(addedFiles)}', raw: true); | |
612 inputList(addedFiles, (file) => buf.write(file)); | |
613 | |
614 h4('ImplicitFiles files ${lenCounter(implicitFiles)}', raw: true); | |
615 inputList(implicitFiles, (file) => buf.write(file)); | |
616 | |
617 SourceFactory sourceFactory = driver.sourceFactory; | |
618 if (sourceFactory is SourceFactoryImpl) { | |
619 h3('Resolvers'); | |
620 for (UriResolver resolver in sourceFactory.resolvers) { | |
621 h4(resolver.runtimeType.toString()); | |
622 buf.write('<p>'); | |
623 if (resolver is DartUriResolver) { | |
624 DartSdk sdk = resolver.dartSdk; | |
625 buf.write(' (sdk = '); | |
626 buf.write(sdk.runtimeType); | |
627 if (sdk is FolderBasedDartSdk) { | |
628 buf.write(' (path = '); | |
629 buf.write(sdk.directory.path); | |
630 buf.write(')'); | |
631 } else if (sdk is EmbedderSdk) { | |
632 buf.write(' (map = '); | |
633 writeMap(sdk.urlMappings); | |
634 buf.write(')'); | |
635 } | |
636 buf.write(')'); | |
637 } else if (resolver is SdkExtUriResolver) { | |
638 buf.write(' (map = '); | |
639 writeMap(resolver.urlMappings); | |
640 buf.write(')'); | |
641 } else if (resolver is PackageMapUriResolver) { | |
642 writeMap(resolver.packageMap); | |
643 } | |
644 buf.write('</p>'); | |
645 } | |
646 } | |
647 } | |
648 | |
649 String describe(AnalysisOptionsImpl options) { | |
650 StringBuffer b = new StringBuffer(); | |
651 | |
652 b.write( | |
653 writeOption('Analyze function bodies', options.analyzeFunctionBodies)); | |
654 b.write(writeOption('Enable asserts in initializer lists', | |
655 options.enableAssertInitializer)); | |
656 b.write(writeOption( | |
657 'Enable strict call checks', options.enableStrictCallChecks)); | |
658 b.write(writeOption('Enable super mixins', options.enableSuperMixins)); | |
659 b.write(writeOption('Generate dart2js hints', options.dart2jsHint)); | |
660 b.write(writeOption( | |
661 'Generate errors in implicit files', options.generateImplicitErrors)); | |
662 b.write( | |
663 writeOption('Generate errors in SDK files', options.generateSdkErrors)); | |
664 b.write(writeOption('Generate hints', options.hint)); | |
665 b.write(writeOption('Incremental resolution', options.incremental)); | |
666 b.write(writeOption( | |
667 'Incremental resolution with API changes', options.incrementalApi)); | |
668 b.write(writeOption('Preserve comments', options.preserveComments)); | |
669 b.write(writeOption('Strong mode', options.strongMode)); | |
670 b.write(writeOption('Strong mode hints', options.strongModeHints)); | |
671 | |
672 return b.toString(); | |
673 } | |
674 | |
675 void writeList<E>(List<E> list) { | |
676 buf.writeln('[${list.join(', ')}]'); | |
677 } | |
678 | |
679 void writeMap<V>(Map<String, V> map) { | |
680 List<String> keys = map.keys.toList(); | |
681 keys.sort(); | |
682 int length = keys.length; | |
683 buf.write('{'); | |
684 for (int i = 0; i < length; i++) { | |
685 buf.write('<br>'); | |
686 String key = keys[i]; | |
687 V value = map[key]; | |
688 buf.write(key); | |
689 buf.write(' = '); | |
690 if (value is List) { | |
691 writeList(value); | |
692 } else { | |
693 buf.write(value); | |
694 } | |
695 buf.write(','); | |
696 } | |
697 buf.write('<br>}'); | |
698 } | |
699 } | |
700 | |
701 class OverlaysPage extends DiagnosticPageWithNav { | |
702 OverlaysPage(DiagnosticsSite site) | |
703 : super(site, 'overlays', 'Overlays', | |
704 description: 'Editing overlays - unsaved file changes.'); | |
705 | |
706 @override | |
707 void generateContent(Map<String, String> params) { | |
708 FileContentOverlay overlays = server.fileContentOverlay; | |
709 List<String> paths = overlays.paths.toList()..sort(); | |
710 | |
711 String overlayPath = params['overlay']; | |
712 if (overlayPath != null) { | |
713 if (overlays[overlayPath] != null) { | |
714 buf.write('<pre><code>'); | |
715 buf.write(overlays[overlayPath]); | |
716 buf.writeln('</code></pre>'); | |
717 } else { | |
718 p('<code>${escape(overlayPath)}</code> not found.', raw: true); | |
719 } | |
720 | |
721 return; | |
722 } | |
723 | |
724 if (paths.isEmpty) { | |
725 blankslate('No overlays.'); | |
726 } else { | |
727 String lenCounter(List list) { | |
728 return '<span class="counter" style="float: right;">${list.length}</span >'; | |
729 } | |
730 | |
731 h3('Overlays ${lenCounter(paths)}', raw: true); | |
732 ul(paths, (String overlayPath) { | |
733 String uri = '$path?overlay=${Uri.encodeQueryComponent(overlayPath)}'; | |
734 buf.writeln('<a href="$uri">${escape(overlayPath)}</a>'); | |
735 }); | |
736 } | |
737 } | |
738 } | |
739 | |
740 class PluginsPage extends DiagnosticPageWithNav { | |
741 PluginsPage(DiagnosticsSite site) | |
742 : super(site, 'plugins', 'Plugins', description: 'Plugins in use.'); | |
743 | |
744 @override | |
745 void generateContent(Map<String, String> params) { | |
746 h3('Analysis plugins'); | |
747 List<PluginInfo> analysisPlugins = server.pluginManager.plugins; | |
748 | |
749 if (analysisPlugins.isEmpty) { | |
750 blankslate('No analysis plugins active.'); | |
751 } else { | |
752 ul(analysisPlugins, (PluginInfo p) { | |
753 buf.writeln('${p.data.name} ${p.pluginId} (${p.data.version})'); | |
754 }); | |
755 } | |
756 | |
757 h3('Analyzer plugins'); | |
758 void writePlugin(Plugin plugin) { | |
759 buf.write(plugin.uniqueIdentifier); | |
760 buf.write(' ('); | |
761 buf.write(plugin.runtimeType); | |
762 buf.write(')'); | |
763 } | |
764 | |
765 List<Plugin> plugins = [ | |
766 AnalysisEngine.instance.enginePlugin, | |
767 server.serverPlugin | |
768 ]; | |
769 plugins.addAll(server.userDefinedPlugins); | |
770 ul(plugins, writePlugin); | |
771 } | |
772 } | |
773 | |
774 class ExecutionDomainPage extends DiagnosticPageWithNav { | |
775 ExecutionDomainPage(DiagnosticsSite site) | |
776 : super(site, 'execution', 'Execution Domain', | |
777 description: 'Data for the analysis server\'s execution domain.'); | |
778 | |
779 @override | |
780 void generateContent(Map<String, String> params) { | |
781 ExecutionDomainHandler domain = server.handlers.firstWhere( | |
782 (handler) => handler is ExecutionDomainHandler, | |
783 orElse: () => null); | |
784 | |
785 h3('Subscriptions'); | |
786 ul(ExecutionService.VALUES, (item) { | |
787 if (domain.onFileAnalyzed != null) { | |
788 buf.write('$item (has subscriptions)'); | |
789 } else { | |
790 buf.write('$item (no subscriptions)'); | |
791 } | |
792 }); | |
793 } | |
794 } | |
795 | |
796 class CompletionPage extends DiagnosticPageWithNav { | |
797 CompletionPage(DiagnosticsSite site) | |
798 : super(site, 'completion', 'Code Completion', | |
799 description: 'Latency statistics for code completion.'); | |
800 | |
801 @override | |
802 void generateContent(Map<String, String> params) { | |
803 CompletionDomainHandler domain = server.handlers.firstWhere( | |
804 (handler) => handler is CompletionDomainHandler, | |
805 orElse: () => null); | |
806 | |
807 List<CompletionPerformance> completions = domain.performanceList.toList(); | |
808 completions.sort((a, b) => b.start.compareTo(a.start)); | |
809 | |
810 if (completions.isEmpty) { | |
811 blankslate('No completions recorded.'); | |
812 return; | |
813 } | |
814 | |
815 int fastCount = | |
816 completions.where((c) => c.elapsedInMilliseconds <= 100).length; | |
817 p('${completions.length} results; ${printPercentage(fastCount / completions. length)} within 100ms.'); | |
818 | |
819 // draw a chart | |
820 buf.writeln( | |
821 '<div id="chart-div" style="width: 700px; height: 300px;"></div>'); | |
822 StringBuffer rowData = new StringBuffer(); | |
823 for (int i = completions.length - 1; i >= 0; i--) { | |
824 // [' ', 101.5] | |
825 if (rowData.isNotEmpty) rowData.write(','); | |
826 rowData.write("[' ', ${completions[i].elapsedInMilliseconds}]"); | |
827 } | |
828 buf.writeln(''' | |
829 <script type="text/javascript"> | |
830 google.charts.load('current', {'packages':['bar']}); | |
831 google.charts.setOnLoadCallback(drawChart); | |
832 function drawChart() { | |
833 var data = google.visualization.arrayToDataTable([ | |
834 ['Completions', 'Time'], | |
835 $rowData | |
836 ]); | |
837 var options = { bars: 'vertical', vAxis: {format: 'decimal'}, height: 30 0 }; | |
838 var chart = new google.charts.Bar(document.getElementById('chart-div')); | |
839 chart.draw(data, google.charts.Bar.convertOptions(options)); | |
840 } | |
841 </script> | |
842 '''); | |
843 | |
844 // emit the data as a table | |
845 buf.writeln('<table>'); | |
846 buf.writeln( | |
847 '<tr><th>Time</th><th>Results</th><th>Source</th><th>Snippet</th></tr>') ; | |
848 for (CompletionPerformance completion in completions) { | |
849 buf.writeln('<tr>' | |
850 '<td class="pre right">${printMilliseconds(completion.elapsedInMillise conds)}</td>' | |
851 '<td class="right">${completion.suggestionCount}</td>' | |
852 '<td>${escape(completion.source.shortName)}</td>' | |
853 '<td><code>${escape(completion.snippet)}</code></td>' | |
854 '</tr>'); | |
855 } | |
856 buf.writeln('</table>'); | |
857 } | |
858 } | |
859 | |
860 // TODO(devoncarew): Show the last x requests and responses. | |
861 class CommunicationsPage extends DiagnosticPageWithNav { | |
862 CommunicationsPage(DiagnosticsSite site) | |
863 : super(site, 'communications', 'Communications', | |
864 description: | |
865 'Latency statistics for analysis server communications.'); | |
866 | |
867 @override | |
868 void generateContent(Map<String, String> params) { | |
869 void writeRow(List<String> data, {List<String> classes}) { | |
870 buf.write("<tr>"); | |
871 for (int i = 0; i < data.length; i++) { | |
872 String c = classes == null ? null : classes[i]; | |
873 if (c != null) { | |
874 buf.write('<td class="$c">${escape(data[i])}</td>'); | |
875 } else { | |
876 buf.write('<td>${escape(data[i])}</td>'); | |
877 } | |
878 } | |
879 buf.writeln("</tr>"); | |
880 } | |
881 | |
882 buf.writeln('<div class="columns">'); | |
883 | |
884 ServerPerformance perf = server.performanceAfterStartup; | |
885 if (perf != null) { | |
886 buf.writeln('<div class="column one-half">'); | |
887 h3('Current'); | |
888 | |
889 int requestCount = perf.requestCount; | |
890 double averageLatency = | |
891 requestCount > 0 ? (perf.requestLatency / requestCount) : 0.0; | |
892 int maximumLatency = perf.maxLatency; | |
893 double slowRequestPercent = | |
894 requestCount > 0 ? (perf.slowRequestCount / requestCount) : 0.0; | |
895 | |
896 buf.write('<table>'); | |
897 writeRow([printInteger(requestCount), 'requests'], | |
898 classes: ["right", null]); | |
899 writeRow([printMilliseconds(averageLatency), 'average latency'], | |
900 classes: ["right", null]); | |
901 writeRow([printMilliseconds(maximumLatency), 'maximum latency'], | |
902 classes: ["right", null]); | |
903 writeRow([printPercentage(slowRequestPercent), '> 150 ms latency'], | |
904 classes: ["right", null]); | |
905 buf.write('</table>'); | |
906 buf.write('</div>'); | |
907 } | |
908 | |
909 buf.writeln('<div class="column one-half">'); | |
910 h3('Startup'); | |
911 perf = server.performanceDuringStartup; | |
912 | |
913 int requestCount = perf.requestCount; | |
914 double averageLatency = | |
915 requestCount > 0 ? (perf.requestLatency / requestCount) : 0.0; | |
916 int maximumLatency = perf.maxLatency; | |
917 double slowRequestPercent = | |
918 requestCount > 0 ? (perf.slowRequestCount / requestCount) : 0.0; | |
919 | |
920 buf.write('<table>'); | |
921 writeRow([printInteger(requestCount), 'requests'], | |
922 classes: ["right", null]); | |
923 writeRow([printMilliseconds(averageLatency), 'average latency'], | |
924 classes: ["right", null]); | |
925 writeRow([printMilliseconds(maximumLatency), 'maximum latency'], | |
926 classes: ["right", null]); | |
927 writeRow([printPercentage(slowRequestPercent), '> 150 ms latency'], | |
928 classes: ["right", null]); | |
929 buf.write('</table>'); | |
930 | |
931 if (server.performanceAfterStartup != null) { | |
932 int startupTime = | |
933 server.performanceAfterStartup.startTime - perf.startTime; | |
934 p('(initial analysis time: ${printMilliseconds(startupTime)})'); | |
935 } | |
936 buf.write('</div>'); | |
937 | |
938 buf.write('</div>'); | |
939 } | |
940 } | |
941 | |
942 String writeOption(String name, dynamic value) { | |
943 return '$name: <code>$value</code><br> '; | |
944 } | |
945 | |
946 String get _sdkVersion { | |
947 String version = Platform.version; | |
948 if (version.contains(' ')) { | |
949 version = version.substring(0, version.indexOf(' ')); | |
950 } | |
951 return version; | |
952 } | |
OLD | NEW |