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(b.title.toLowerCase()))); |
| 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) { |
| 260 buf.write('<span class="counter">$detail</span>'); |
| 261 } |
| 262 buf.writeln('</a>'); |
| 263 } |
| 264 buf.writeln('</nav>'); |
| 265 buf.writeln('</div>'); |
| 266 |
| 267 buf.writeln('<div class="four-fifths column markdown-body">'); |
| 268 h1(title, classes: 'page-title'); |
| 269 div(() { |
| 270 p(description); |
| 271 generateContent(params); |
| 272 }, classes: 'markdown-body'); |
| 273 buf.writeln('</div>'); |
| 274 |
| 275 buf.writeln('</div>'); |
| 276 } |
| 277 |
| 278 String get navDetail => null; |
| 279 |
| 280 bool get isNavPage => true; |
| 281 } |
| 282 |
| 283 class NotFoundPage extends DiagnosticPage { |
| 284 final String path; |
| 285 |
| 286 NotFoundPage(Site site, this.path) |
| 287 : super(site, '', '404 Not found', description: "'$path' not found."); |
| 288 |
| 289 void generateContent(Map<String, String> params) {} |
| 290 } |
| 291 |
| 292 class ExceptionPage extends DiagnosticPage { |
| 293 final StackTrace trace; |
| 294 |
| 295 ExceptionPage(Site site, String message, this.trace) |
| 296 : super(site, '', '500 Oops', description: message); |
| 297 |
| 298 void generateContent(Map<String, String> params) { |
| 299 p(trace.toString(), style: 'white-space: pre'); |
| 300 } |
| 301 } |
| 302 |
| 303 class FeedbackPage extends DiagnosticPage { |
| 304 FeedbackPage(DiagnosticsSite site) |
| 305 : super(site, 'feedback', 'Feedback', |
| 306 description: 'Providing feedback and filing issues.'); |
| 307 |
| 308 @override |
| 309 void generateContent(Map<String, String> params) { |
| 310 final String issuesUrl = 'https://github.com/dart-lang/sdk/issues'; |
| 311 p( |
| 312 'To file issues or feature requests, see our ' |
| 313 '<a href="$issuesUrl">bug tracker</a>. When filing an issue, please de
scribe:', |
| 314 raw: true, |
| 315 ); |
| 316 ul([ |
| 317 'what you were doing', |
| 318 'what occured', |
| 319 'what you think the expected behavior should have been', |
| 320 ], (line) => buf.writeln(line)); |
| 321 |
| 322 p('Other data to include:'); |
| 323 ul([ |
| 324 "the IDE you are using and it's version", |
| 325 'the Dart SDK version (<code>${escape(_sdkVersion)}</code>)', |
| 326 'your operating system (<code>${escape(Platform.operatingSystem)}</code>)'
, |
| 327 ], (line) => buf.writeln(line)); |
| 328 |
| 329 p('Thanks!'); |
| 330 } |
| 331 } |
| 332 |
| 333 class StatusPage extends DiagnosticPageWithNav { |
| 334 StatusPage(DiagnosticsSite site) |
| 335 : super(site, 'status', 'Status', |
| 336 description: |
| 337 'General status and diagnostics for the analysis server.'); |
| 338 |
| 339 @override |
| 340 void generateContent(Map<String, String> params) { |
| 341 buf.writeln('<div class="columns">'); |
| 342 |
| 343 buf.writeln('<div class="column one-half">'); |
| 344 h3('Status'); |
| 345 buf.writeln(writeOption( |
| 346 'New analysis driver enabled', server.options.enableNewAnalysisDriver)); |
| 347 buf.writeln(writeOption('Instrumentation enabled', |
| 348 AnalysisEngine.instance.instrumentationService.isActive)); |
| 349 buf.writeln(writeOption('Server process ID', pid)); |
| 350 buf.writeln('</div>'); |
| 351 |
| 352 buf.writeln('<div class="column one-half">'); |
| 353 h3('Versions'); |
| 354 buf.writeln(writeOption('Analysis server version', AnalysisServer.VERSION)); |
| 355 buf.writeln(writeOption('Dart SDK', Platform.version)); |
| 356 buf.writeln('</div>'); |
| 357 |
| 358 buf.writeln('</div>'); |
| 359 |
| 360 h3('Server domain subscriptions'); |
| 361 ul(ServerService.VALUES, (item) { |
| 362 if (server.serverServices.contains(item)) { |
| 363 buf.write('$item (has subscriptions)'); |
| 364 } else { |
| 365 buf.write('$item (no subscriptions)'); |
| 366 } |
| 367 }); |
| 368 |
| 369 h3('Analysis domain subscriptions'); |
| 370 for (AnalysisService service in AnalysisService.VALUES) { |
| 371 buf.writeln('${service.name}<br>'); |
| 372 ul(server.analysisServices[service] ?? [], (item) { |
| 373 buf.write('$item'); |
| 374 }); |
| 375 } |
| 376 |
| 377 List<String> lines = (site as DiagnosticsSite).lastPrintedLines; |
| 378 if (lines.isNotEmpty) { |
| 379 h3('Debug output'); |
| 380 p(lines.join('\n'), style: 'white-space: pre'); |
| 381 } |
| 382 } |
| 383 } |
| 384 |
| 385 class InstrumentationPage extends DiagnosticPageWithNav { |
| 386 InstrumentationPage(DiagnosticsSite site) |
| 387 : super(site, 'instrumentation', 'Instrumentation', |
| 388 description: |
| 389 'Verbose instrumentation data from the analysis server.'); |
| 390 |
| 391 @override |
| 392 void generateContent(Map<String, String> params) { |
| 393 p( |
| 394 'Instrumentation can be enabled by starting the analysis server with the
' |
| 395 '<code>--instrumentation-log-file=path/to/file</code> flag.', |
| 396 raw: true); |
| 397 |
| 398 if (!AnalysisEngine.instance.instrumentationService.isActive) { |
| 399 blankslate('Instrumentation not active.'); |
| 400 return; |
| 401 } |
| 402 |
| 403 h3('Instrumentation'); |
| 404 |
| 405 p('Instrumentation active.'); |
| 406 |
| 407 InstrumentationServer instrumentation = |
| 408 AnalysisEngine.instance.instrumentationService.instrumentationServer; |
| 409 String description = instrumentation.describe; |
| 410 HtmlEscape htmlEscape = new HtmlEscape(HtmlEscapeMode.ELEMENT); |
| 411 description = htmlEscape.convert(description); |
| 412 // Convert http(s): references to hyperlinks. |
| 413 final RegExp urlRegExp = new RegExp(r'[http|https]+:\/*(\S+)'); |
| 414 description = description.replaceAllMapped(urlRegExp, (Match match) { |
| 415 return '<a href="${match.group(0)}">${match.group(1)}</a>'; |
| 416 }); |
| 417 p(description.replaceAll('\n', '<br>'), raw: true); |
| 418 } |
| 419 } |
| 420 |
| 421 class ProfilePage extends DiagnosticPageWithNav { |
| 422 ProfilePage(DiagnosticsSite site) |
| 423 : super(site, 'profile', 'Profiling Info', |
| 424 description: 'Profiling performance tag data.'); |
| 425 |
| 426 @override |
| 427 void generateContent(Map<String, String> params) { |
| 428 // prepare sorted tags |
| 429 List<PerformanceTag> tags = PerformanceTag.all.toList(); |
| 430 tags.remove(ServerPerformanceStatistics.idle); |
| 431 tags.removeWhere((tag) => tag.label == 'unknown'); |
| 432 tags.sort((a, b) => b.elapsedMs - a.elapsedMs); |
| 433 |
| 434 // draw a pie chart |
| 435 String rowData = |
| 436 tags.map((tag) => "['${tag.label}', ${tag.elapsedMs}]").join(','); |
| 437 buf.writeln( |
| 438 '<div id="chart-div" style="width: 700px; height: 300px;"></div>'); |
| 439 buf.writeln(''' |
| 440 <script type="text/javascript"> |
| 441 google.charts.load('current', {'packages':['corechart']}); |
| 442 google.charts.setOnLoadCallback(drawChart); |
| 443 |
| 444 function drawChart() { |
| 445 var data = new google.visualization.DataTable(); |
| 446 data.addColumn('string', 'Tag'); |
| 447 data.addColumn('number', 'Time (ms)'); |
| 448 data.addRows([$rowData]); |
| 449 var options = {'title': 'Performance Tag Data', 'width': 700, 'height'
: 300}; |
| 450 var chart = new google.visualization.PieChart(document.getElementById(
'chart-div')); |
| 451 chart.draw(data, options); |
| 452 } |
| 453 </script> |
| 454 '''); |
| 455 |
| 456 // print total time |
| 457 int totalTime = |
| 458 tags.fold<int>(0, (int a, PerformanceTag tag) => a + tag.elapsedMs); |
| 459 p('Total measured time: ${printMilliseconds(totalTime)}'); |
| 460 |
| 461 // write out a table |
| 462 void _writeRow(List<String> data, {bool header: false}) { |
| 463 buf.write('<tr>'); |
| 464 if (header) { |
| 465 for (String d in data) { |
| 466 buf.write('<th>$d</th>'); |
| 467 } |
| 468 } else { |
| 469 for (String d in data) { |
| 470 buf.write('<td>$d</td>'); |
| 471 } |
| 472 } |
| 473 buf.writeln('</tr>'); |
| 474 } |
| 475 |
| 476 buf.write('<table>'); |
| 477 _writeRow(['Tag name', 'Time (in ms)', 'Percent'], header: true); |
| 478 void writeRow(PerformanceTag tag) { |
| 479 double percent = tag.elapsedMs / totalTime; |
| 480 _writeRow([ |
| 481 tag.label, |
| 482 printMilliseconds(tag.elapsedMs), |
| 483 printPercentage(percent) |
| 484 ]); |
| 485 } |
| 486 |
| 487 tags.forEach(writeRow); |
| 488 buf.write('</table>'); |
| 489 } |
| 490 } |
| 491 |
| 492 // TODO(devoncarew): This is not hooked up. |
| 493 class ExceptionsPage extends DiagnosticPageWithNav { |
| 494 ExceptionsPage(DiagnosticsSite site) |
| 495 : super(site, 'exceptions', 'Exceptions', |
| 496 description: 'Exceptions from the analysis server.'); |
| 497 |
| 498 String get navDetail => exceptions.length.toString(); |
| 499 |
| 500 @override |
| 501 void generateContent(Map<String, String> params) { |
| 502 if (exceptions.isEmpty) { |
| 503 blankslate('No exceptions encountered!'); |
| 504 } else { |
| 505 for (CaughtException ex in exceptions) { |
| 506 h3('${ex.exception}'); |
| 507 p(ex.toString(), style: 'white-space: pre'); |
| 508 } |
| 509 } |
| 510 } |
| 511 |
| 512 // TODO: Implement - read this from a server exception ring buffer. |
| 513 List<CaughtException> get exceptions => []; |
| 514 } |
| 515 |
| 516 class ContextsPage extends DiagnosticPageWithNav { |
| 517 ContextsPage(DiagnosticsSite site) |
| 518 : super(site, 'contexts', 'Contexts', |
| 519 description: |
| 520 'An analysis context defines the options and the set of sources
being analyzed.'); |
| 521 |
| 522 String get navDetail => printInteger(server.driverMap.length); |
| 523 |
| 524 @override |
| 525 void generateContent(Map<String, String> params) { |
| 526 Map<Folder, AnalysisDriver> driverMap = server.driverMap; |
| 527 if (driverMap.isEmpty) { |
| 528 blankslate('No contexts.'); |
| 529 return; |
| 530 } |
| 531 |
| 532 String contextPath = params['context']; |
| 533 Folder folder = driverMap.keys |
| 534 .firstWhere((f) => f.path == contextPath, orElse: () => null); |
| 535 |
| 536 if (folder == null) { |
| 537 folder = driverMap.keys.first; |
| 538 contextPath = folder.path; |
| 539 } |
| 540 |
| 541 AnalysisDriver driver = driverMap[folder]; |
| 542 |
| 543 buf.writeln('<div class="tabnav">'); |
| 544 buf.writeln('<nav class="tabnav-tabs">'); |
| 545 for (Folder f in driverMap.keys) { |
| 546 if (f == folder) { |
| 547 buf.writeln( |
| 548 '<a class="tabnav-tab selected">${escape(f.shortName)}</a>'); |
| 549 } else { |
| 550 String p = '$path?context=${Uri.encodeQueryComponent(f.path)}'; |
| 551 buf.writeln( |
| 552 '<a href="$p" class="tabnav-tab">${escape(f.shortName)}</a>'); |
| 553 } |
| 554 } |
| 555 buf.writeln('</nav>'); |
| 556 buf.writeln('</div>'); |
| 557 |
| 558 buf.writeln(writeOption('Context location', escape(contextPath))); |
| 559 buf.writeln(writeOption('Analysis options path', |
| 560 escape(driver.contextRoot.optionsFilePath ?? 'none'))); |
| 561 |
| 562 buf.writeln('<div class="columns">'); |
| 563 |
| 564 buf.writeln('<div class="column one-half">'); |
| 565 h3('Analysis options'); |
| 566 p(describe(driver.analysisOptions), raw: true); |
| 567 buf.writeln( |
| 568 writeOption('Has .packages file', folder.getChild('.packages').exists)); |
| 569 buf.writeln(writeOption( |
| 570 'Has pubspec.yaml file', folder.getChild('pubspec.yaml').exists)); |
| 571 buf.writeln('</div>'); |
| 572 |
| 573 buf.writeln('<div class="column one-half">'); |
| 574 DartSdk sdk = driver?.sourceFactory?.dartSdk; |
| 575 AnalysisOptionsImpl sdkOptions = sdk?.context?.analysisOptions; |
| 576 if (sdkOptions != null) { |
| 577 h3('SDK analysis options'); |
| 578 p(describe(sdkOptions), raw: true); |
| 579 |
| 580 if (sdk is FolderBasedDartSdk) { |
| 581 p(writeOption('Use summaries', sdk.useSummary), raw: true); |
| 582 } |
| 583 } |
| 584 buf.writeln('</div>'); |
| 585 |
| 586 buf.writeln('</div>'); |
| 587 |
| 588 h3('Lints'); |
| 589 p(driver.analysisOptions.lintRules.map((l) => l.name).join(', ')); |
| 590 |
| 591 h3('Error processors'); |
| 592 p(driver.analysisOptions.errorProcessors |
| 593 .map((e) => e.description) |
| 594 .join(', ')); |
| 595 |
| 596 List<String> priorityFiles = driver.priorityFiles; |
| 597 List<String> addedFiles = driver.addedFiles.toList(); |
| 598 List<String> implicitFiles = |
| 599 driver.knownFiles.difference(driver.addedFiles).toList(); |
| 600 addedFiles.sort(); |
| 601 implicitFiles.sort(); |
| 602 |
| 603 String lenCounter(List list) { |
| 604 return '<span class="counter" style="float: right;">${list.length}</span>'
; |
| 605 } |
| 606 |
| 607 h3('Context files'); |
| 608 |
| 609 h4('Priority files ${lenCounter(priorityFiles)}', raw: true); |
| 610 inputList(priorityFiles, (file) => buf.write(file)); |
| 611 |
| 612 h4('Added files ${lenCounter(addedFiles)}', raw: true); |
| 613 inputList(addedFiles, (file) => buf.write(file)); |
| 614 |
| 615 h4('ImplicitFiles files ${lenCounter(implicitFiles)}', raw: true); |
| 616 inputList(implicitFiles, (file) => buf.write(file)); |
| 617 |
| 618 SourceFactory sourceFactory = driver.sourceFactory; |
| 619 if (sourceFactory is SourceFactoryImpl) { |
| 620 h3('Resolvers'); |
| 621 for (UriResolver resolver in sourceFactory.resolvers) { |
| 622 h4(resolver.runtimeType.toString()); |
| 623 buf.write('<p>'); |
| 624 if (resolver is DartUriResolver) { |
| 625 DartSdk sdk = resolver.dartSdk; |
| 626 buf.write(' (sdk = '); |
| 627 buf.write(sdk.runtimeType); |
| 628 if (sdk is FolderBasedDartSdk) { |
| 629 buf.write(' (path = '); |
| 630 buf.write(sdk.directory.path); |
| 631 buf.write(')'); |
| 632 } else if (sdk is EmbedderSdk) { |
| 633 buf.write(' (map = '); |
| 634 writeMap(sdk.urlMappings); |
| 635 buf.write(')'); |
| 636 } |
| 637 buf.write(')'); |
| 638 } else if (resolver is SdkExtUriResolver) { |
| 639 buf.write(' (map = '); |
| 640 writeMap(resolver.urlMappings); |
| 641 buf.write(')'); |
| 642 } else if (resolver is PackageMapUriResolver) { |
| 643 writeMap(resolver.packageMap); |
| 644 } |
| 645 buf.write('</p>'); |
| 646 } |
| 647 } |
| 648 } |
| 649 |
| 650 String describe(AnalysisOptionsImpl options) { |
| 651 StringBuffer b = new StringBuffer(); |
| 652 |
| 653 b.write( |
| 654 writeOption('Analyze function bodies', options.analyzeFunctionBodies)); |
| 655 b.write(writeOption('Enable asserts in initializer lists', |
| 656 options.enableAssertInitializer)); |
| 657 b.write(writeOption( |
| 658 'Enable strict call checks', options.enableStrictCallChecks)); |
| 659 b.write(writeOption('Enable super mixins', options.enableSuperMixins)); |
| 660 b.write(writeOption('Generate dart2js hints', options.dart2jsHint)); |
| 661 b.write(writeOption( |
| 662 'Generate errors in implicit files', options.generateImplicitErrors)); |
| 663 b.write( |
| 664 writeOption('Generate errors in SDK files', options.generateSdkErrors)); |
| 665 b.write(writeOption('Generate hints', options.hint)); |
| 666 b.write(writeOption('Incremental resolution', options.incremental)); |
| 667 b.write(writeOption( |
| 668 'Incremental resolution with API changes', options.incrementalApi)); |
| 669 b.write(writeOption('Preserve comments', options.preserveComments)); |
| 670 b.write(writeOption('Strong mode', options.strongMode)); |
| 671 b.write(writeOption('Strong mode hints', options.strongModeHints)); |
| 672 |
| 673 return b.toString(); |
| 674 } |
| 675 |
| 676 void writeList<E>(List<E> list) { |
| 677 buf.writeln('[${list.join(', ')}]'); |
| 678 } |
| 679 |
| 680 void writeMap<V>(Map<String, V> map) { |
| 681 List<String> keys = map.keys.toList(); |
| 682 keys.sort(); |
| 683 int length = keys.length; |
| 684 buf.write('{'); |
| 685 for (int i = 0; i < length; i++) { |
| 686 buf.write('<br>'); |
| 687 String key = keys[i]; |
| 688 V value = map[key]; |
| 689 buf.write(key); |
| 690 buf.write(' = '); |
| 691 if (value is List) { |
| 692 writeList(value); |
| 693 } else { |
| 694 buf.write(value); |
| 695 } |
| 696 buf.write(','); |
| 697 } |
| 698 buf.write('<br>}'); |
| 699 } |
| 700 } |
| 701 |
| 702 class OverlaysPage extends DiagnosticPageWithNav { |
| 703 OverlaysPage(DiagnosticsSite site) |
| 704 : super(site, 'overlays', 'Overlays', |
| 705 description: 'Editing overlays - unsaved file changes.'); |
| 706 |
| 707 @override |
| 708 void generateContent(Map<String, String> params) { |
| 709 FileContentOverlay overlays = server.fileContentOverlay; |
| 710 List<String> paths = overlays.paths.toList()..sort(); |
| 711 |
| 712 String overlayPath = params['overlay']; |
| 713 if (overlayPath != null) { |
| 714 if (overlays[overlayPath] != null) { |
| 715 buf.write('<pre><code>'); |
| 716 buf.write(overlays[overlayPath]); |
| 717 buf.writeln('</code></pre>'); |
| 718 } else { |
| 719 p('<code>${escape(overlayPath)}</code> not found.', raw: true); |
| 720 } |
| 721 |
| 722 return; |
| 723 } |
| 724 |
| 725 if (paths.isEmpty) { |
| 726 blankslate('No overlays.'); |
| 727 } else { |
| 728 String lenCounter(List list) { |
| 729 return '<span class="counter" style="float: right;">${list.length}</span
>'; |
| 730 } |
| 731 |
| 732 h3('Overlays ${lenCounter(paths)}', raw: true); |
| 733 ul(paths, (String overlayPath) { |
| 734 String uri = '$path?overlay=${Uri.encodeQueryComponent(overlayPath)}'; |
| 735 buf.writeln('<a href="$uri">${escape(overlayPath)}</a>'); |
| 736 }); |
| 737 } |
| 738 } |
| 739 } |
| 740 |
| 741 class PluginsPage extends DiagnosticPageWithNav { |
| 742 PluginsPage(DiagnosticsSite site) |
| 743 : super(site, 'plugins', 'Plugins', description: 'Plugins in use.'); |
| 744 |
| 745 @override |
| 746 void generateContent(Map<String, String> params) { |
| 747 h3('Analysis plugins'); |
| 748 List<PluginInfo> analysisPlugins = server.pluginManager.plugins; |
| 749 |
| 750 if (analysisPlugins.isEmpty) { |
| 751 blankslate('No analysis plugins active.'); |
| 752 } else { |
| 753 ul(analysisPlugins, (PluginInfo p) { |
| 754 buf.writeln('${p.data.name} ${p.pluginId} (${p.data.version})'); |
| 755 }); |
| 756 } |
| 757 |
| 758 h3('Analyzer plugins'); |
| 759 void writePlugin(Plugin plugin) { |
| 760 buf.write(plugin.uniqueIdentifier); |
| 761 buf.write(' ('); |
| 762 buf.write(plugin.runtimeType); |
| 763 buf.write(')'); |
| 764 } |
| 765 |
| 766 List<Plugin> plugins = [ |
| 767 AnalysisEngine.instance.enginePlugin, |
| 768 server.serverPlugin |
| 769 ]; |
| 770 plugins.addAll(server.userDefinedPlugins); |
| 771 ul(plugins, writePlugin); |
| 772 } |
| 773 } |
| 774 |
| 775 class ExecutionDomainPage extends DiagnosticPageWithNav { |
| 776 ExecutionDomainPage(DiagnosticsSite site) |
| 777 : super(site, 'execution', 'Execution Domain', |
| 778 description: 'Data for the analysis server\'s execution domain.'); |
| 779 |
| 780 @override |
| 781 void generateContent(Map<String, String> params) { |
| 782 ExecutionDomainHandler domain = server.handlers.firstWhere( |
| 783 (handler) => handler is ExecutionDomainHandler, |
| 784 orElse: () => null); |
| 785 |
| 786 h3('Subscriptions'); |
| 787 ul(ExecutionService.VALUES, (item) { |
| 788 if (domain.onFileAnalyzed != null) { |
| 789 buf.write('$item (has subscriptions)'); |
| 790 } else { |
| 791 buf.write('$item (no subscriptions)'); |
| 792 } |
| 793 }); |
| 794 } |
| 795 } |
| 796 |
| 797 class CompletionPage extends DiagnosticPageWithNav { |
| 798 CompletionPage(DiagnosticsSite site) |
| 799 : super(site, 'completion', 'Code Completion', |
| 800 description: 'Latency statistics for code completion.'); |
| 801 |
| 802 @override |
| 803 void generateContent(Map<String, String> params) { |
| 804 CompletionDomainHandler domain = server.handlers.firstWhere( |
| 805 (handler) => handler is CompletionDomainHandler, |
| 806 orElse: () => null); |
| 807 |
| 808 List<CompletionPerformance> completions = domain.performanceList.toList(); |
| 809 completions.sort((a, b) => b.start.compareTo(a.start)); |
| 810 |
| 811 if (completions.isEmpty) { |
| 812 blankslate('No completions recorded.'); |
| 813 return; |
| 814 } |
| 815 |
| 816 int fastCount = |
| 817 completions.where((c) => c.elapsedInMilliseconds <= 100).length; |
| 818 p('${completions.length} results; ${printPercentage(fastCount / completions.
length)} within 100ms.'); |
| 819 |
| 820 // draw a chart |
| 821 buf.writeln( |
| 822 '<div id="chart-div" style="width: 700px; height: 300px;"></div>'); |
| 823 StringBuffer rowData = new StringBuffer(); |
| 824 for (int i = completions.length - 1; i >= 0; i--) { |
| 825 // [' ', 101.5] |
| 826 if (rowData.isNotEmpty) { |
| 827 rowData.write(','); |
| 828 } |
| 829 rowData.write("[' ', ${completions[i].elapsedInMilliseconds}]"); |
| 830 } |
| 831 buf.writeln(''' |
| 832 <script type="text/javascript"> |
| 833 google.charts.load('current', {'packages':['bar']}); |
| 834 google.charts.setOnLoadCallback(drawChart); |
| 835 function drawChart() { |
| 836 var data = google.visualization.arrayToDataTable([ |
| 837 ['Completions', 'Time'], |
| 838 $rowData |
| 839 ]); |
| 840 var options = { bars: 'vertical', vAxis: {format: 'decimal'}, height: 30
0 }; |
| 841 var chart = new google.charts.Bar(document.getElementById('chart-div')); |
| 842 chart.draw(data, google.charts.Bar.convertOptions(options)); |
| 843 } |
| 844 </script> |
| 845 '''); |
| 846 |
| 847 // emit the data as a table |
| 848 buf.writeln('<table>'); |
| 849 buf.writeln( |
| 850 '<tr><th>Time</th><th>Results</th><th>Source</th><th>Snippet</th></tr>')
; |
| 851 for (CompletionPerformance completion in completions) { |
| 852 buf.writeln('<tr>' |
| 853 '<td class="pre right">${printMilliseconds(completion.elapsedInMillise
conds)}</td>' |
| 854 '<td class="right">${completion.suggestionCount}</td>' |
| 855 '<td>${escape(completion.source.shortName)}</td>' |
| 856 '<td><code>${escape(completion.snippet)}</code></td>' |
| 857 '</tr>'); |
| 858 } |
| 859 buf.writeln('</table>'); |
| 860 } |
| 861 } |
| 862 |
| 863 // TODO(devoncarew): Show the last x requests and responses. |
| 864 class CommunicationsPage extends DiagnosticPageWithNav { |
| 865 CommunicationsPage(DiagnosticsSite site) |
| 866 : super(site, 'communications', 'Communications', |
| 867 description: |
| 868 'Latency statistics for analysis server communications.'); |
| 869 |
| 870 @override |
| 871 void generateContent(Map<String, String> params) { |
| 872 void writeRow(List<String> data, {List<String> classes}) { |
| 873 buf.write("<tr>"); |
| 874 for (int i = 0; i < data.length; i++) { |
| 875 String c = classes == null ? null : classes[i]; |
| 876 if (c != null) { |
| 877 buf.write('<td class="$c">${escape(data[i])}</td>'); |
| 878 } else { |
| 879 buf.write('<td>${escape(data[i])}</td>'); |
| 880 } |
| 881 } |
| 882 buf.writeln("</tr>"); |
| 883 } |
| 884 |
| 885 buf.writeln('<div class="columns">'); |
| 886 |
| 887 ServerPerformance perf = server.performanceAfterStartup; |
| 888 if (perf != null) { |
| 889 buf.writeln('<div class="column one-half">'); |
| 890 h3('Current'); |
| 891 |
| 892 int requestCount = perf.requestCount; |
| 893 double averageLatency = |
| 894 requestCount > 0 ? (perf.requestLatency / requestCount) : 0.0; |
| 895 int maximumLatency = perf.maxLatency; |
| 896 double slowRequestPercent = |
| 897 requestCount > 0 ? (perf.slowRequestCount / requestCount) : 0.0; |
| 898 |
| 899 buf.write('<table>'); |
| 900 writeRow([printInteger(requestCount), 'requests'], |
| 901 classes: ["right", null]); |
| 902 writeRow([printMilliseconds(averageLatency), 'average latency'], |
| 903 classes: ["right", null]); |
| 904 writeRow([printMilliseconds(maximumLatency), 'maximum latency'], |
| 905 classes: ["right", null]); |
| 906 writeRow([printPercentage(slowRequestPercent), '> 150 ms latency'], |
| 907 classes: ["right", null]); |
| 908 buf.write('</table>'); |
| 909 buf.write('</div>'); |
| 910 } |
| 911 |
| 912 buf.writeln('<div class="column one-half">'); |
| 913 h3('Startup'); |
| 914 perf = server.performanceDuringStartup; |
| 915 |
| 916 int requestCount = perf.requestCount; |
| 917 double averageLatency = |
| 918 requestCount > 0 ? (perf.requestLatency / requestCount) : 0.0; |
| 919 int maximumLatency = perf.maxLatency; |
| 920 double slowRequestPercent = |
| 921 requestCount > 0 ? (perf.slowRequestCount / requestCount) : 0.0; |
| 922 |
| 923 buf.write('<table>'); |
| 924 writeRow([printInteger(requestCount), 'requests'], |
| 925 classes: ["right", null]); |
| 926 writeRow([printMilliseconds(averageLatency), 'average latency'], |
| 927 classes: ["right", null]); |
| 928 writeRow([printMilliseconds(maximumLatency), 'maximum latency'], |
| 929 classes: ["right", null]); |
| 930 writeRow([printPercentage(slowRequestPercent), '> 150 ms latency'], |
| 931 classes: ["right", null]); |
| 932 buf.write('</table>'); |
| 933 |
| 934 if (server.performanceAfterStartup != null) { |
| 935 int startupTime = |
| 936 server.performanceAfterStartup.startTime - perf.startTime; |
| 937 p('(initial analysis time: ${printMilliseconds(startupTime)})'); |
| 938 } |
| 939 buf.write('</div>'); |
| 940 |
| 941 buf.write('</div>'); |
| 942 } |
| 943 } |
| 944 |
| 945 String writeOption(String name, dynamic value) { |
| 946 return '$name: <code>$value</code><br> '; |
| 947 } |
| 948 |
| 949 String get _sdkVersion { |
| 950 String version = Platform.version; |
| 951 if (version.contains(' ')) { |
| 952 version = version.substring(0, version.indexOf(' ')); |
| 953 } |
| 954 return version; |
| 955 } |
OLD | NEW |