Chromium Code Reviews| 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 |