| Index: pkg/analysis_server/lib/src/status/diagnostics.dart
|
| diff --git a/pkg/analysis_server/lib/src/status/diagnostics.dart b/pkg/analysis_server/lib/src/status/diagnostics.dart
|
| index 5455a10fc88fe32b0d54a900c8ecbfb5f4d6d416..ef961c033841bd025ec52a9728d96123d448aa54 100644
|
| --- a/pkg/analysis_server/lib/src/status/diagnostics.dart
|
| +++ b/pkg/analysis_server/lib/src/status/diagnostics.dart
|
| @@ -131,6 +131,18 @@ td.pre {
|
| }
|
| ''';
|
|
|
| +String get _sdkVersion {
|
| + String version = Platform.version;
|
| + if (version.contains(' ')) {
|
| + version = version.substring(0, version.indexOf(' '));
|
| + }
|
| + return version;
|
| +}
|
| +
|
| +String writeOption(String name, dynamic value) {
|
| + return '$name: <code>$value</code><br> ';
|
| +}
|
| +
|
| class AstPage extends DiagnosticPageWithNav {
|
| String _description;
|
|
|
| @@ -141,17 +153,6 @@ class AstPage extends DiagnosticPageWithNav {
|
| String get description => _description ?? super.description;
|
|
|
| @override
|
| - Future<Null> generatePage(Map<String, String> params) async {
|
| - try {
|
| - String path = params['file'];
|
| - _description = path == null ? null : 'The AST for $path.';
|
| - await super.generatePage(params);
|
| - } finally {
|
| - _description = null;
|
| - }
|
| - }
|
| -
|
| - @override
|
| Future<Null> generateContent(Map<String, String> params) async {
|
| String path = params['file'];
|
| if (path == null) {
|
| @@ -174,421 +175,594 @@ class AstPage extends DiagnosticPageWithNav {
|
| AstWriter writer = new AstWriter(buf);
|
| result.unit.accept(writer);
|
| }
|
| -}
|
|
|
| -class DiagnosticsSite extends Site implements AbstractGetHandler {
|
| - /// An object that can handle either a WebSocket connection or a connection
|
| - /// to the client over stdio.
|
| - SocketServer socketServer;
|
| -
|
| - /// The last few lines printed.
|
| - List<String> lastPrintedLines = <String>[];
|
| + @override
|
| + Future<Null> generatePage(Map<String, String> params) async {
|
| + try {
|
| + String path = params['file'];
|
| + _description = path == null ? null : 'The AST for $path.';
|
| + await super.generatePage(params);
|
| + } finally {
|
| + _description = null;
|
| + }
|
| + }
|
| +}
|
|
|
| - DiagnosticsSite(this.socketServer, this.lastPrintedLines)
|
| - : super('Analysis Server') {
|
| - pages.add(new CompletionPage(this));
|
| - pages.add(new CommunicationsPage(this));
|
| - pages.add(new ContextsPage(this));
|
| - pages.add(new ExceptionsPage(this));
|
| - pages.add(new InstrumentationPage(this));
|
| - pages.add(new OverlaysPage(this));
|
| - pages.add(new PluginsPage(this));
|
| - pages.add(new ProfilePage(this));
|
| - pages.add(new SubscriptionsPage(this));
|
| +class CommunicationsPage extends DiagnosticPageWithNav {
|
| + CommunicationsPage(DiagnosticsSite site)
|
| + : super(site, 'communications', 'Communications',
|
| + description:
|
| + 'Latency statistics for analysis server communications.');
|
|
|
| - ProcessProfiler profiler = ProcessProfiler.getProfilerForPlatform();
|
| - if (profiler != null) {
|
| - pages.add(new MemoryAndCpuPage(this, profiler));
|
| + @override
|
| + void generateContent(Map<String, String> params) {
|
| + void writeRow(List<String> data, {List<String> classes}) {
|
| + buf.write("<tr>");
|
| + for (int i = 0; i < data.length; i++) {
|
| + String c = classes == null ? null : classes[i];
|
| + if (c != null) {
|
| + buf.write('<td class="$c">${escape(data[i])}</td>');
|
| + } else {
|
| + buf.write('<td>${escape(data[i])}</td>');
|
| + }
|
| + }
|
| + buf.writeln("</tr>");
|
| }
|
|
|
| - pages.sort(((Page a, Page b) =>
|
| - a.title.toLowerCase().compareTo(b.title.toLowerCase())));
|
| -
|
| - // Add the status page at the beginning.
|
| - pages.insert(0, new StatusPage(this));
|
| + buf.writeln('<div class="columns">');
|
|
|
| - // Add non-nav pages.
|
| - pages.add(new FeedbackPage(this));
|
| + ServerPerformance perf = server.performanceAfterStartup;
|
| + if (perf != null) {
|
| + buf.writeln('<div class="column one-half">');
|
| + h3('Current');
|
|
|
| - secondaryPages.add(new AstPage(this));
|
| - secondaryPages.add(new ElementModelPage(this));
|
| - }
|
| + int requestCount = perf.requestCount;
|
| + int averageLatency =
|
| + requestCount > 0 ? (perf.requestLatency ~/ requestCount) : 0;
|
| + int maximumLatency = perf.maxLatency;
|
| + double slowRequestPercent =
|
| + requestCount > 0 ? (perf.slowRequestCount / requestCount) : 0.0;
|
|
|
| - String get customCss => kCustomCss;
|
| + buf.write('<table>');
|
| + writeRow([printInteger(requestCount), 'requests'],
|
| + classes: ["right", null]);
|
| + writeRow([printMilliseconds(averageLatency), 'average latency'],
|
| + classes: ["right", null]);
|
| + writeRow([printMilliseconds(maximumLatency), 'maximum latency'],
|
| + classes: ["right", null]);
|
| + writeRow([printPercentage(slowRequestPercent), '> 150 ms latency'],
|
| + classes: ["right", null]);
|
| + buf.write('</table>');
|
|
|
| - Page createUnknownPage(String unknownPath) =>
|
| - new NotFoundPage(this, unknownPath);
|
| + String time = server.uptime.toString();
|
| + if (time.contains('.')) {
|
| + time = time.substring(0, time.indexOf('.'));
|
| + }
|
| + buf.writeln(writeOption('Uptime', time));
|
|
|
| - Page createExceptionPage(String message, StackTrace trace) =>
|
| - new ExceptionPage(this, message, trace);
|
| -}
|
| + buf.write('</div>');
|
| + }
|
|
|
| -/// A page with a proscriptive notion of layout.
|
| -abstract class DiagnosticPage extends Page {
|
| - final Site site;
|
| + buf.writeln('<div class="column one-half">');
|
| + h3('Startup');
|
| + perf = server.performanceDuringStartup;
|
|
|
| - DiagnosticPage(this.site, String id, String title, {String description})
|
| - : super(id, title, description: description);
|
| + int requestCount = perf.requestCount;
|
| + int averageLatency =
|
| + requestCount > 0 ? (perf.requestLatency ~/ requestCount) : 0;
|
| + int maximumLatency = perf.maxLatency;
|
| + double slowRequestPercent =
|
| + requestCount > 0 ? (perf.slowRequestCount / requestCount) : 0.0;
|
|
|
| - AnalysisServer get server =>
|
| - (site as DiagnosticsSite).socketServer.analysisServer;
|
| + buf.write('<table>');
|
| + writeRow([printInteger(requestCount), 'requests'],
|
| + classes: ["right", null]);
|
| + writeRow([printMilliseconds(averageLatency), 'average latency'],
|
| + classes: ["right", null]);
|
| + writeRow([printMilliseconds(maximumLatency), 'maximum latency'],
|
| + classes: ["right", null]);
|
| + writeRow([printPercentage(slowRequestPercent), '> 150 ms latency'],
|
| + classes: ["right", null]);
|
| + buf.write('</table>');
|
|
|
| - Future<Null> generatePage(Map<String, String> params) async {
|
| - buf.writeln('<!DOCTYPE html><html lang="en">');
|
| - buf.write('<head>');
|
| - buf.write('<meta charset="utf-8">');
|
| - buf.write('<meta name="viewport" content="width=device-width, '
|
| - 'initial-scale=1.0">');
|
| - buf.writeln('<title>${site.title}</title>');
|
| - buf.writeln('<link rel="stylesheet" '
|
| - 'href="https://cdnjs.cloudflare.com/ajax/libs/Primer/6.0.0/build.css">');
|
| - buf.writeln('<link rel="stylesheet" '
|
| - 'href="https://cdnjs.cloudflare.com/ajax/libs/octicons/4.4.0/font/octicons.css">');
|
| - buf.writeln('<script type="text/javascript" '
|
| - 'src="https://www.gstatic.com/charts/loader.js"></script>');
|
| - buf.writeln('<style>${site.customCss}</style>');
|
| - buf.writeln('</head>');
|
| + if (server.performanceAfterStartup != null) {
|
| + int startupTime =
|
| + server.performanceAfterStartup.startTime - perf.startTime;
|
| + buf.writeln(
|
| + writeOption('Initial analysis time', printMilliseconds(startupTime)));
|
| + }
|
| + buf.write('</div>');
|
|
|
| - buf.writeln('<body>');
|
| - generateHeader();
|
| - buf.writeln('<div class="container">');
|
| - await generateContainer(params);
|
| - generateFooter();
|
| - buf.writeln('</div>'); // div.container
|
| - buf.writeln('</body>');
|
| - buf.writeln('</html>');
|
| + buf.write('</div>');
|
| }
|
| +}
|
|
|
| - void generateHeader() {
|
| - buf.writeln('''
|
| - <header class="masthead">
|
| - <div class="container">
|
| - <span class="masthead-logo">
|
| - <span class="mega-octicon octicon-database"></span>
|
| - ${site.title} Diagnostics
|
| - </span>
|
| +class CompletionPage extends DiagnosticPageWithNav {
|
| + CompletionPage(DiagnosticsSite site)
|
| + : super(site, 'completion', 'Code Completion',
|
| + description: 'Latency statistics for code completion.');
|
|
|
| - <nav class="masthead-nav">
|
| - <a href="/status" ${isNavPage ? ' class="active"' : ''}>Diagnostics</a>
|
| - <a href="/feedback" ${isCurrentPage('/feedback') ? ' class="active"' : ''}>Feedback</a>
|
| - <a href="https://www.dartlang.org/tools/analyzer" target="_blank">Docs</a>
|
| - <a href="https://htmlpreview.github.io/?https://github.com/dart-lang/sdk/blob/master/pkg/analysis_server/doc/api.html" target="_blank">Spec</a>
|
| - </nav>
|
| - </div>
|
| - </header>
|
| -''');
|
| - }
|
| + @override
|
| + void generateContent(Map<String, String> params) {
|
| + CompletionDomainHandler completionDomain = server.handlers
|
| + .firstWhere((handler) => handler is CompletionDomainHandler);
|
|
|
| - Future<Null> generateContainer(Map<String, String> params) async {
|
| - buf.writeln('<div class="columns docs-layout">');
|
| - buf.writeln('<div class="three-fourths column markdown-body">');
|
| - h1(title, classes: 'page-title');
|
| - await asyncDiv(() async {
|
| - p(description);
|
| - await generateContent(params);
|
| - }, classes: 'markdown-body');
|
| - buf.writeln('</div>');
|
| - buf.writeln('</div>');
|
| - }
|
| + List<CompletionPerformance> completions =
|
| + completionDomain.performanceList.items.toList();
|
|
|
| - void generateContent(Map<String, String> params);
|
| + if (completions.isEmpty) {
|
| + blankslate('No completions recorded.');
|
| + return;
|
| + }
|
|
|
| - void generateFooter() {
|
| + int fastCount =
|
| + completions.where((c) => c.elapsedInMilliseconds <= 100).length;
|
| + p('${completions.length} results; ${printPercentage(fastCount / completions.length)} within 100ms.');
|
| +
|
| + // draw a chart
|
| + buf.writeln(
|
| + '<div id="chart-div" style="width: 700px; height: 300px;"></div>');
|
| + StringBuffer rowData = new StringBuffer();
|
| + for (int i = completions.length - 1; i >= 0; i--) {
|
| + // [' ', 101.5]
|
| + if (rowData.isNotEmpty) {
|
| + rowData.write(',');
|
| + }
|
| + rowData.write("[' ', ${completions[i].elapsedInMilliseconds}]");
|
| + }
|
| buf.writeln('''
|
| - <footer class="footer">
|
| - Dart ${site.title} <span style="float:right">SDK ${_sdkVersion}</span>
|
| - </footer>
|
| + <script type="text/javascript">
|
| + google.charts.load('current', {'packages':['bar']});
|
| + google.charts.setOnLoadCallback(drawChart);
|
| + function drawChart() {
|
| + var data = google.visualization.arrayToDataTable([
|
| + ['Completions', 'Time'],
|
| + $rowData
|
| + ]);
|
| + var options = { bars: 'vertical', vAxis: {format: 'decimal'}, height: 300 };
|
| + var chart = new google.charts.Bar(document.getElementById('chart-div'));
|
| + chart.draw(data, google.charts.Bar.convertOptions(options));
|
| + }
|
| + </script>
|
| ''');
|
| - }
|
| -
|
| - bool get isNavPage => false;
|
| -}
|
|
|
| -abstract class DiagnosticPageWithNav extends DiagnosticPage {
|
| - DiagnosticPageWithNav(Site site, String id, String title,
|
| - {String description})
|
| - : super(site, id, title, description: description);
|
| + // emit the data as a table
|
| + buf.writeln('<table>');
|
| + buf.writeln(
|
| + '<tr><th>Time</th><th>Results</th><th>Source</th><th>Snippet</th></tr>');
|
| + for (CompletionPerformance completion in completions) {
|
| + buf.writeln('<tr>'
|
| + '<td class="pre right">${printMilliseconds(completion.elapsedInMilliseconds)}</td>'
|
| + '<td class="right">${completion.suggestionCount}</td>'
|
| + '<td>${escape(completion.source.shortName)}</td>'
|
| + '<td><code>${escape(completion.snippet)}</code></td>'
|
| + '</tr>');
|
| + }
|
| + buf.writeln('</table>');
|
| + }
|
| +}
|
|
|
| - Future<Null> generateContainer(Map<String, String> params) async {
|
| - buf.writeln('<div class="columns docs-layout">');
|
| +class ContextsPage extends DiagnosticPageWithNav {
|
| + ContextsPage(DiagnosticsSite site)
|
| + : super(site, 'contexts', 'Contexts',
|
| + description:
|
| + 'An analysis context defines the options and the set of sources being analyzed.');
|
|
|
| - buf.writeln('<div class="one-fifth column">');
|
| - buf.writeln('<nav class="menu docs-menu">');
|
| - for (Page page in site.pages.where((p) => p is DiagnosticPageWithNav)) {
|
| - buf.write('<a class="menu-item ${page == this ? ' selected' : ''}" '
|
| - 'href="${page.path}">${escape(page.title)}');
|
| - String detail = (page as DiagnosticPageWithNav).navDetail;
|
| - if (detail != null) {
|
| - buf.write('<span class="counter">$detail</span>');
|
| + String get navDetail => printInteger(server.driverMap.length);
|
| +
|
| + String describe(AnalysisOptionsImpl options) {
|
| + StringBuffer b = new StringBuffer();
|
| +
|
| + b.write(
|
| + writeOption('Analyze function bodies', options.analyzeFunctionBodies));
|
| + b.write(writeOption('Enable asserts in initializer lists',
|
| + options.enableAssertInitializer));
|
| + b.write(writeOption(
|
| + 'Enable strict call checks', options.enableStrictCallChecks));
|
| + b.write(writeOption('Enable super mixins', options.enableSuperMixins));
|
| + b.write(writeOption('Generate dart2js hints', options.dart2jsHint));
|
| + b.write(writeOption(
|
| + 'Generate errors in implicit files', options.generateImplicitErrors));
|
| + b.write(
|
| + writeOption('Generate errors in SDK files', options.generateSdkErrors));
|
| + b.write(writeOption('Generate hints', options.hint));
|
| + b.write(writeOption('Incremental resolution', options.incremental));
|
| + b.write(writeOption(
|
| + 'Incremental resolution with API changes', options.incrementalApi));
|
| + b.write(writeOption('Preserve comments', options.preserveComments));
|
| + b.write(writeOption('Strong mode', options.strongMode));
|
| + b.write(writeOption('Strong mode hints', options.strongModeHints));
|
| +
|
| + return b.toString();
|
| + }
|
| +
|
| + @override
|
| + void generateContent(Map<String, String> params) {
|
| + Map<Folder, AnalysisDriver> driverMap = server.driverMap;
|
| + if (driverMap.isEmpty) {
|
| + blankslate('No contexts.');
|
| + return;
|
| + }
|
| +
|
| + String contextPath = params['context'];
|
| + List<Folder> folders = driverMap.keys.toList();
|
| + folders
|
| + .sort((first, second) => first.shortName.compareTo(second.shortName));
|
| + Folder folder =
|
| + folders.firstWhere((f) => f.path == contextPath, orElse: () => null);
|
| +
|
| + if (folder == null) {
|
| + folder = folders.first;
|
| + contextPath = folder.path;
|
| + }
|
| +
|
| + AnalysisDriver driver = driverMap[folder];
|
| +
|
| + buf.writeln('<div class="tabnav">');
|
| + buf.writeln('<nav class="tabnav-tabs">');
|
| + for (Folder f in folders) {
|
| + if (f == folder) {
|
| + buf.writeln(
|
| + '<a class="tabnav-tab selected">${escape(f.shortName)}</a>');
|
| + } else {
|
| + String p = '$path?context=${Uri.encodeQueryComponent(f.path)}';
|
| + buf.writeln(
|
| + '<a href="$p" class="tabnav-tab">${escape(f.shortName)}</a>');
|
| }
|
| - buf.writeln('</a>');
|
| }
|
| buf.writeln('</nav>');
|
| buf.writeln('</div>');
|
|
|
| - buf.writeln('<div class="four-fifths column markdown-body">');
|
| - h1(title, classes: 'page-title');
|
| - await asyncDiv(() async {
|
| - p(description);
|
| - await generateContent(params);
|
| - }, classes: 'markdown-body');
|
| - buf.writeln('</div>');
|
| + buf.writeln(writeOption('Context location', escape(contextPath)));
|
| + buf.writeln(writeOption('Analysis options path',
|
| + escape(driver.contextRoot.optionsFilePath ?? 'none')));
|
| +
|
| + buf.writeln('<div class="columns">');
|
|
|
| + buf.writeln('<div class="column one-half">');
|
| + h3('Analysis options');
|
| + p(describe(driver.analysisOptions), raw: true);
|
| + buf.writeln(
|
| + writeOption('Has .packages file', folder.getChild('.packages').exists));
|
| + buf.writeln(writeOption(
|
| + 'Has pubspec.yaml file', folder.getChild('pubspec.yaml').exists));
|
| buf.writeln('</div>');
|
| - }
|
|
|
| - String get navDetail => null;
|
| + buf.writeln('<div class="column one-half">');
|
| + DartSdk sdk = driver?.sourceFactory?.dartSdk;
|
| + AnalysisOptionsImpl sdkOptions = sdk?.context?.analysisOptions;
|
| + if (sdkOptions != null) {
|
| + h3('SDK analysis options');
|
| + p(describe(sdkOptions), raw: true);
|
|
|
| - bool get isNavPage => true;
|
| -}
|
| + if (sdk is FolderBasedDartSdk) {
|
| + p(writeOption('Use summaries', sdk.useSummary), raw: true);
|
| + }
|
| + }
|
| + buf.writeln('</div>');
|
|
|
| -class ElementModelPage extends DiagnosticPageWithNav {
|
| - String _description;
|
| + buf.writeln('</div>');
|
|
|
| - ElementModelPage(DiagnosticsSite site)
|
| - : super(site, 'element', 'Element model',
|
| - description: 'The element model for a file.');
|
| + h3('Lints');
|
| + p(driver.analysisOptions.lintRules.map((l) => l.name).join(', '));
|
|
|
| - @override
|
| - String get description => _description ?? super.description;
|
| + h3('Error processors');
|
| + p(driver.analysisOptions.errorProcessors
|
| + .map((e) => e.description)
|
| + .join(', '));
|
|
|
| - @override
|
| - Future<Null> generatePage(Map<String, String> params) async {
|
| - try {
|
| - String path = params['file'];
|
| - _description = path == null ? null : 'The element model for $path.';
|
| - await super.generatePage(params);
|
| - } finally {
|
| - _description = null;
|
| - }
|
| - }
|
| + List<String> priorityFiles = driver.priorityFiles;
|
| + List<String> addedFiles = driver.addedFiles.toList();
|
| + List<String> implicitFiles =
|
| + driver.knownFiles.difference(driver.addedFiles).toList();
|
| + addedFiles.sort();
|
| + implicitFiles.sort();
|
|
|
| - @override
|
| - Future<Null> generateContent(Map<String, String> params) async {
|
| - String path = params['file'];
|
| - if (path == null) {
|
| - p('No file path provided.');
|
| - return;
|
| + String lenCounter(List list) {
|
| + return '<span class="counter" style="float: right;">${list.length}</span>';
|
| }
|
| - AnalysisDriver driver = server.getAnalysisDriver(path);
|
| - if (driver == null) {
|
| - p('The file <code>${escape(path)}</code> is not being analyzed.',
|
| - raw: true);
|
| - return;
|
| +
|
| + h3('Context files');
|
| +
|
| + void writeFile(String file) {
|
| + String astPath = '/ast?file=${Uri.encodeQueryComponent(file)}';
|
| + String elementPath = '/element?file=${Uri.encodeQueryComponent(file)}';
|
| +
|
| + buf.write(file);
|
| + buf.write(' (');
|
| + buf.writeln('<a href="$astPath">ast</a>');
|
| + buf.write(' ');
|
| + buf.writeln('<a href="$elementPath">element</a>');
|
| + buf.write(')');
|
| }
|
| - AnalysisResult result = await driver.getResult(path);
|
| - if (result == null) {
|
| - p('An element model could not be produced for the file <code>${escape(path)}</code>.',
|
| - raw: true);
|
| - return;
|
| +
|
| + h4('Priority files ${lenCounter(priorityFiles)}', raw: true);
|
| + ul(priorityFiles, writeFile, classes: 'scroll-table');
|
| +
|
| + h4('Added files ${lenCounter(addedFiles)}', raw: true);
|
| + ul(addedFiles, writeFile, classes: 'scroll-table');
|
| +
|
| + h4('ImplicitFiles files ${lenCounter(implicitFiles)}', raw: true);
|
| + ul(implicitFiles, writeFile, classes: 'scroll-table');
|
| +
|
| + SourceFactory sourceFactory = driver.sourceFactory;
|
| + if (sourceFactory is SourceFactoryImpl) {
|
| + h3('Resolvers');
|
| + for (UriResolver resolver in sourceFactory.resolvers) {
|
| + h4(resolver.runtimeType.toString());
|
| + buf.write('<p class="scroll-table">');
|
| + if (resolver is DartUriResolver) {
|
| + DartSdk sdk = resolver.dartSdk;
|
| + buf.write(' (sdk = ');
|
| + buf.write(sdk.runtimeType);
|
| + if (sdk is FolderBasedDartSdk) {
|
| + buf.write(' (path = ');
|
| + buf.write(sdk.directory.path);
|
| + buf.write(')');
|
| + } else if (sdk is EmbedderSdk) {
|
| + buf.write(' (map = ');
|
| + writeMap(sdk.urlMappings);
|
| + buf.write(')');
|
| + }
|
| + buf.write(')');
|
| + } else if (resolver is SdkExtUriResolver) {
|
| + buf.write(' (map = ');
|
| + writeMap(resolver.urlMappings);
|
| + buf.write(')');
|
| + } else if (resolver is PackageMapUriResolver) {
|
| + writeMap(resolver.packageMap);
|
| + }
|
| + buf.write('</p>');
|
| + }
|
| }
|
| + }
|
|
|
| - ElementWriter writer = new ElementWriter(buf);
|
| - result.unit.element.accept(writer);
|
| + void writeList<E>(List<E> list) {
|
| + buf.writeln('[${list.join(', ')}]');
|
| }
|
| -}
|
|
|
| -class NotFoundPage extends DiagnosticPage {
|
| - final String path;
|
| + void writeMap<V>(Map<String, V> map) {
|
| + List<String> keys = map.keys.toList();
|
| + keys.sort();
|
| + int length = keys.length;
|
| + buf.write('{');
|
| + for (int i = 0; i < length; i++) {
|
| + buf.write('<br>');
|
| + String key = keys[i];
|
| + V value = map[key];
|
| + buf.write(key);
|
| + buf.write(' = ');
|
| + if (value is List) {
|
| + writeList(value);
|
| + } else {
|
| + buf.write(value);
|
| + }
|
| + buf.write(',');
|
| + }
|
| + buf.write('<br>}');
|
| + }
|
| +}
|
|
|
| - NotFoundPage(Site site, this.path)
|
| - : super(site, '', '404 Not found', description: "'$path' not found.");
|
| +/// A page with a proscriptive notion of layout.
|
| +abstract class DiagnosticPage extends Page {
|
| + final Site site;
|
|
|
| - void generateContent(Map<String, String> params) {}
|
| -}
|
| + DiagnosticPage(this.site, String id, String title, {String description})
|
| + : super(id, title, description: description);
|
|
|
| -class ExceptionPage extends DiagnosticPage {
|
| - final StackTrace trace;
|
| + bool get isNavPage => false;
|
|
|
| - ExceptionPage(Site site, String message, this.trace)
|
| - : super(site, '', '500 Oops', description: message);
|
| + AnalysisServer get server =>
|
| + (site as DiagnosticsSite).socketServer.analysisServer;
|
|
|
| - void generateContent(Map<String, String> params) {
|
| - p(trace.toString(), style: 'white-space: pre');
|
| + Future<Null> generateContainer(Map<String, String> params) async {
|
| + buf.writeln('<div class="columns docs-layout">');
|
| + buf.writeln('<div class="three-fourths column markdown-body">');
|
| + h1(title, classes: 'page-title');
|
| + await asyncDiv(() async {
|
| + p(description);
|
| + await generateContent(params);
|
| + }, classes: 'markdown-body');
|
| + buf.writeln('</div>');
|
| + buf.writeln('</div>');
|
| }
|
| -}
|
|
|
| -class FeedbackPage extends DiagnosticPage {
|
| - FeedbackPage(DiagnosticsSite site)
|
| - : super(site, 'feedback', 'Feedback',
|
| - description: 'Providing feedback and filing issues.');
|
| + void generateContent(Map<String, String> params);
|
|
|
| - @override
|
| - void generateContent(Map<String, String> params) {
|
| - final String issuesUrl = 'https://github.com/dart-lang/sdk/issues';
|
| - p(
|
| - 'To file issues or feature requests, see our '
|
| - '<a href="$issuesUrl">bug tracker</a>. When filing an issue, please describe:',
|
| - raw: true,
|
| - );
|
| - ul([
|
| - 'what you were doing',
|
| - 'what occured',
|
| - 'what you think the expected behavior should have been',
|
| - ], (line) => buf.writeln(line));
|
| + void generateFooter() {
|
| + buf.writeln('''
|
| + <footer class="footer">
|
| + Dart ${site.title} <span style="float:right">SDK ${_sdkVersion}</span>
|
| + </footer>
|
| +''');
|
| + }
|
|
|
| - List<String> ideInfo = [];
|
| - if (server.options.clientId != null) {
|
| - ideInfo.add(server.options.clientId);
|
| - }
|
| - if (server.options.clientVersion != null) {
|
| - ideInfo.add(server.options.clientVersion);
|
| - }
|
| - String ideText = ideInfo.map((str) => '<code>$str</code>').join(', ');
|
| + void generateHeader() {
|
| + buf.writeln('''
|
| + <header class="masthead">
|
| + <div class="container">
|
| + <span class="masthead-logo">
|
| + <span class="mega-octicon octicon-database"></span>
|
| + ${site.title} Diagnostics
|
| + </span>
|
|
|
| - p('Other data to include:');
|
| - ul([
|
| - "the IDE you are using and it's version${ideText.isEmpty ? '' : ' ($ideText)'}",
|
| - 'the Dart SDK version (<code>${escape(_sdkVersion)}</code>)',
|
| - 'your operating system (<code>${escape(Platform.operatingSystem)}</code>)',
|
| - ], (line) => buf.writeln(line));
|
| + <nav class="masthead-nav">
|
| + <a href="/status" ${isNavPage ? ' class="active"' : ''}>Diagnostics</a>
|
| + <a href="/feedback" ${isCurrentPage('/feedback') ? ' class="active"' : ''}>Feedback</a>
|
| + <a href="https://www.dartlang.org/tools/analyzer" target="_blank">Docs</a>
|
| + <a href="https://htmlpreview.github.io/?https://github.com/dart-lang/sdk/blob/master/pkg/analysis_server/doc/api.html" target="_blank">Spec</a>
|
| + </nav>
|
| + </div>
|
| + </header>
|
| +''');
|
| + }
|
|
|
| - p('Thanks!');
|
| + Future<Null> generatePage(Map<String, String> params) async {
|
| + buf.writeln('<!DOCTYPE html><html lang="en">');
|
| + buf.write('<head>');
|
| + buf.write('<meta charset="utf-8">');
|
| + buf.write('<meta name="viewport" content="width=device-width, '
|
| + 'initial-scale=1.0">');
|
| + buf.writeln('<title>${site.title}</title>');
|
| + buf.writeln('<link rel="stylesheet" '
|
| + 'href="https://cdnjs.cloudflare.com/ajax/libs/Primer/6.0.0/build.css">');
|
| + buf.writeln('<link rel="stylesheet" '
|
| + 'href="https://cdnjs.cloudflare.com/ajax/libs/octicons/4.4.0/font/octicons.css">');
|
| + buf.writeln('<script type="text/javascript" '
|
| + 'src="https://www.gstatic.com/charts/loader.js"></script>');
|
| + buf.writeln('<style>${site.customCss}</style>');
|
| + buf.writeln('</head>');
|
| +
|
| + buf.writeln('<body>');
|
| + generateHeader();
|
| + buf.writeln('<div class="container">');
|
| + await generateContainer(params);
|
| + generateFooter();
|
| + buf.writeln('</div>'); // div.container
|
| + buf.writeln('</body>');
|
| + buf.writeln('</html>');
|
| }
|
| }
|
|
|
| -class StatusPage extends DiagnosticPageWithNav {
|
| - StatusPage(DiagnosticsSite site)
|
| - : super(site, 'status', 'Status',
|
| - description:
|
| - 'General status and diagnostics for the analysis server.');
|
| +abstract class DiagnosticPageWithNav extends DiagnosticPage {
|
| + DiagnosticPageWithNav(Site site, String id, String title,
|
| + {String description})
|
| + : super(site, id, title, description: description);
|
|
|
| - @override
|
| - void generateContent(Map<String, String> params) {
|
| - buf.writeln('<div class="columns">');
|
| + bool get isNavPage => true;
|
|
|
| - buf.writeln('<div class="column one-half">');
|
| - h3('Status');
|
| - buf.writeln(writeOption(
|
| - 'New analysis driver enabled', server.options.enableNewAnalysisDriver));
|
| - buf.writeln(writeOption('Instrumentation enabled',
|
| - AnalysisEngine.instance.instrumentationService.isActive));
|
| - buf.writeln(writeOption('Server process ID', pid));
|
| - buf.writeln('</div>');
|
| + String get navDetail => null;
|
|
|
| - buf.writeln('<div class="column one-half">');
|
| - h3('Versions');
|
| - buf.writeln(writeOption('Analysis server version', AnalysisServer.VERSION));
|
| - buf.writeln(writeOption('Dart SDK', Platform.version));
|
| + Future<Null> generateContainer(Map<String, String> params) async {
|
| + buf.writeln('<div class="columns docs-layout">');
|
| +
|
| + buf.writeln('<div class="one-fifth column">');
|
| + buf.writeln('<nav class="menu docs-menu">');
|
| + for (Page page in site.pages.where((p) => p is DiagnosticPageWithNav)) {
|
| + buf.write('<a class="menu-item ${page == this ? ' selected' : ''}" '
|
| + 'href="${page.path}">${escape(page.title)}');
|
| + String detail = (page as DiagnosticPageWithNav).navDetail;
|
| + if (detail != null) {
|
| + buf.write('<span class="counter">$detail</span>');
|
| + }
|
| + buf.writeln('</a>');
|
| + }
|
| + buf.writeln('</nav>');
|
| buf.writeln('</div>');
|
|
|
| + buf.writeln('<div class="four-fifths column markdown-body">');
|
| + h1(title, classes: 'page-title');
|
| + await asyncDiv(() async {
|
| + p(description);
|
| + await generateContent(params);
|
| + }, classes: 'markdown-body');
|
| buf.writeln('</div>');
|
|
|
| - List<String> lines = (site as DiagnosticsSite).lastPrintedLines;
|
| - if (lines.isNotEmpty) {
|
| - h3('Debug output');
|
| - p(lines.join('\n'), style: 'white-space: pre');
|
| - }
|
| + buf.writeln('</div>');
|
| }
|
| }
|
|
|
| -class InstrumentationPage extends DiagnosticPageWithNav {
|
| - InstrumentationPage(DiagnosticsSite site)
|
| - : super(site, 'instrumentation', 'Instrumentation',
|
| - description:
|
| - 'Verbose instrumentation data from the analysis server.');
|
| +class DiagnosticsSite extends Site implements AbstractGetHandler {
|
| + /// An object that can handle either a WebSocket connection or a connection
|
| + /// to the client over stdio.
|
| + SocketServer socketServer;
|
|
|
| - @override
|
| - void generateContent(Map<String, String> params) {
|
| - p(
|
| - 'Instrumentation can be enabled by starting the analysis server with the '
|
| - '<code>--instrumentation-log-file=path/to/file</code> flag.',
|
| - raw: true);
|
| + /// The last few lines printed.
|
| + List<String> lastPrintedLines = <String>[];
|
| +
|
| + DiagnosticsSite(this.socketServer, this.lastPrintedLines)
|
| + : super('Analysis Server') {
|
| + pages.add(new CompletionPage(this));
|
| + pages.add(new CommunicationsPage(this));
|
| + pages.add(new ContextsPage(this));
|
| + pages.add(new ExceptionsPage(this));
|
| + pages.add(new InstrumentationPage(this));
|
| + pages.add(new OverlaysPage(this));
|
| + pages.add(new PluginsPage(this));
|
| + pages.add(new ProfilePage(this));
|
| + pages.add(new SubscriptionsPage(this));
|
|
|
| - if (!AnalysisEngine.instance.instrumentationService.isActive) {
|
| - blankslate('Instrumentation not active.');
|
| - return;
|
| + ProcessProfiler profiler = ProcessProfiler.getProfilerForPlatform();
|
| + if (profiler != null) {
|
| + pages.add(new MemoryAndCpuPage(this, profiler));
|
| }
|
|
|
| - h3('Instrumentation');
|
| + pages.sort(((Page a, Page b) =>
|
| + a.title.toLowerCase().compareTo(b.title.toLowerCase())));
|
|
|
| - p('Instrumentation active.');
|
| + // Add the status page at the beginning.
|
| + pages.insert(0, new StatusPage(this));
|
|
|
| - InstrumentationServer instrumentation =
|
| - AnalysisEngine.instance.instrumentationService.instrumentationServer;
|
| - String description = instrumentation.describe;
|
| - HtmlEscape htmlEscape = new HtmlEscape(HtmlEscapeMode.ELEMENT);
|
| - description = htmlEscape.convert(description);
|
| - // Convert http(s): references to hyperlinks.
|
| - final RegExp urlRegExp = new RegExp(r'[http|https]+:\/*(\S+)');
|
| - description = description.replaceAllMapped(urlRegExp, (Match match) {
|
| - return '<a href="${match.group(0)}">${match.group(1)}</a>';
|
| - });
|
| - p(description.replaceAll('\n', '<br>'), raw: true);
|
| + // Add non-nav pages.
|
| + pages.add(new FeedbackPage(this));
|
| +
|
| + secondaryPages.add(new AstPage(this));
|
| + secondaryPages.add(new ElementModelPage(this));
|
| }
|
| -}
|
|
|
| -class ProfilePage extends DiagnosticPageWithNav {
|
| - ProfilePage(DiagnosticsSite site)
|
| - : super(site, 'profile', 'Profiling Info',
|
| - description: 'Profiling performance tag data.');
|
| + String get customCss => kCustomCss;
|
|
|
| - @override
|
| - void generateContent(Map<String, String> params) {
|
| - // prepare sorted tags
|
| - List<PerformanceTag> tags = PerformanceTag.all.toList();
|
| - tags.remove(ServerPerformanceStatistics.idle);
|
| - tags.remove(PerformanceTag.unknown);
|
| - tags.removeWhere((tag) => tag.elapsedMs == 0);
|
| - tags.sort((a, b) => b.elapsedMs - a.elapsedMs);
|
| + Page createExceptionPage(String message, StackTrace trace) =>
|
| + new ExceptionPage(this, message, trace);
|
|
|
| - // draw a pie chart
|
| - String rowData =
|
| - tags.map((tag) => "['${tag.label}', ${tag.elapsedMs}]").join(',');
|
| - buf.writeln(
|
| - '<div id="chart-div" style="width: 700px; height: 300px;"></div>');
|
| - buf.writeln('''
|
| - <script type="text/javascript">
|
| - google.charts.load('current', {'packages':['corechart']});
|
| - google.charts.setOnLoadCallback(drawChart);
|
| + Page createUnknownPage(String unknownPath) =>
|
| + new NotFoundPage(this, unknownPath);
|
| +}
|
|
|
| - function drawChart() {
|
| - var data = new google.visualization.DataTable();
|
| - data.addColumn('string', 'Tag');
|
| - data.addColumn('number', 'Time (ms)');
|
| - data.addRows([$rowData]);
|
| - var options = {'title': 'Performance Tag Data', 'width': 700, 'height': 300};
|
| - var chart = new google.visualization.PieChart(document.getElementById('chart-div'));
|
| - chart.draw(data, options);
|
| - }
|
| - </script>
|
| -''');
|
| +class ElementModelPage extends DiagnosticPageWithNav {
|
| + String _description;
|
|
|
| - // print total time
|
| - int totalTime =
|
| - tags.fold<int>(0, (int a, PerformanceTag tag) => a + tag.elapsedMs);
|
| - p('Total measured time: ${printMilliseconds(totalTime)}');
|
| + ElementModelPage(DiagnosticsSite site)
|
| + : super(site, 'element', 'Element model',
|
| + description: 'The element model for a file.');
|
|
|
| - // write out a table
|
| - void _writeRow(List<String> data, {bool header: false}) {
|
| - buf.write('<tr>');
|
| - if (header) {
|
| - for (String d in data) {
|
| - buf.write('<th>$d</th>');
|
| - }
|
| - } else {
|
| - buf.write('<td>${data[0]}</td>');
|
| + @override
|
| + String get description => _description ?? super.description;
|
|
|
| - for (String d in data.sublist(1)) {
|
| - buf.write('<td class="right">$d</td>');
|
| - }
|
| - }
|
| - buf.writeln('</tr>');
|
| + @override
|
| + Future<Null> generateContent(Map<String, String> params) async {
|
| + String path = params['file'];
|
| + if (path == null) {
|
| + p('No file path provided.');
|
| + return;
|
| + }
|
| + AnalysisDriver driver = server.getAnalysisDriver(path);
|
| + if (driver == null) {
|
| + p('The file <code>${escape(path)}</code> is not being analyzed.',
|
| + raw: true);
|
| + return;
|
| + }
|
| + AnalysisResult result = await driver.getResult(path);
|
| + if (result == null) {
|
| + p('An element model could not be produced for the file <code>${escape(path)}</code>.',
|
| + raw: true);
|
| + return;
|
| }
|
|
|
| - buf.write('<table>');
|
| - _writeRow(['Tag name', 'Time (in ms)', 'Percent'], header: true);
|
| - void writeRow(PerformanceTag tag) {
|
| - double percent = tag.elapsedMs / totalTime;
|
| - _writeRow([
|
| - tag.label,
|
| - printMilliseconds(tag.elapsedMs),
|
| - printPercentage(percent)
|
| - ]);
|
| + ElementWriter writer = new ElementWriter(buf);
|
| + result.unit.element.accept(writer);
|
| + }
|
| +
|
| + @override
|
| + Future<Null> generatePage(Map<String, String> params) async {
|
| + try {
|
| + String path = params['file'];
|
| + _description = path == null ? null : 'The element model for $path.';
|
| + await super.generatePage(params);
|
| + } finally {
|
| + _description = null;
|
| }
|
| + }
|
| +}
|
|
|
| - tags.forEach(writeRow);
|
| - buf.write('</table>');
|
| +class ExceptionPage extends DiagnosticPage {
|
| + final StackTrace trace;
|
| +
|
| + ExceptionPage(Site site, String message, this.trace)
|
| + : super(site, '', '500 Oops', description: message);
|
| +
|
| + void generateContent(Map<String, String> params) {
|
| + p(trace.toString(), style: 'white-space: pre');
|
| }
|
| }
|
|
|
| @@ -597,6 +771,8 @@ class ExceptionsPage extends DiagnosticPageWithNav {
|
| : super(site, 'exceptions', 'Exceptions',
|
| description: 'Exceptions from the analysis server.');
|
|
|
| + Iterable<ServerException> get exceptions => server.exceptions.items;
|
| +
|
| String get navDetail => printInteger(exceptions.length);
|
|
|
| @override
|
| @@ -614,208 +790,80 @@ class ExceptionsPage extends DiagnosticPageWithNav {
|
| }
|
| }
|
| }
|
| -
|
| - Iterable<ServerException> get exceptions => server.exceptions.items;
|
| }
|
|
|
| -class ContextsPage extends DiagnosticPageWithNav {
|
| - ContextsPage(DiagnosticsSite site)
|
| - : super(site, 'contexts', 'Contexts',
|
| - description:
|
| - 'An analysis context defines the options and the set of sources being analyzed.');
|
| -
|
| - String get navDetail => printInteger(server.driverMap.length);
|
| +class FeedbackPage extends DiagnosticPage {
|
| + FeedbackPage(DiagnosticsSite site)
|
| + : super(site, 'feedback', 'Feedback',
|
| + description: 'Providing feedback and filing issues.');
|
|
|
| @override
|
| void generateContent(Map<String, String> params) {
|
| - Map<Folder, AnalysisDriver> driverMap = server.driverMap;
|
| - if (driverMap.isEmpty) {
|
| - blankslate('No contexts.');
|
| - return;
|
| - }
|
| -
|
| - String contextPath = params['context'];
|
| - List<Folder> folders = driverMap.keys.toList();
|
| - folders
|
| - .sort((first, second) => first.shortName.compareTo(second.shortName));
|
| - Folder folder =
|
| - folders.firstWhere((f) => f.path == contextPath, orElse: () => null);
|
| -
|
| - if (folder == null) {
|
| - folder = folders.first;
|
| - contextPath = folder.path;
|
| - }
|
| -
|
| - AnalysisDriver driver = driverMap[folder];
|
| -
|
| - buf.writeln('<div class="tabnav">');
|
| - buf.writeln('<nav class="tabnav-tabs">');
|
| - for (Folder f in folders) {
|
| - if (f == folder) {
|
| - buf.writeln(
|
| - '<a class="tabnav-tab selected">${escape(f.shortName)}</a>');
|
| - } else {
|
| - String p = '$path?context=${Uri.encodeQueryComponent(f.path)}';
|
| - buf.writeln(
|
| - '<a href="$p" class="tabnav-tab">${escape(f.shortName)}</a>');
|
| - }
|
| - }
|
| - buf.writeln('</nav>');
|
| - buf.writeln('</div>');
|
| -
|
| - buf.writeln(writeOption('Context location', escape(contextPath)));
|
| - buf.writeln(writeOption('Analysis options path',
|
| - escape(driver.contextRoot.optionsFilePath ?? 'none')));
|
| -
|
| - buf.writeln('<div class="columns">');
|
| -
|
| - buf.writeln('<div class="column one-half">');
|
| - h3('Analysis options');
|
| - p(describe(driver.analysisOptions), raw: true);
|
| - buf.writeln(
|
| - writeOption('Has .packages file', folder.getChild('.packages').exists));
|
| - buf.writeln(writeOption(
|
| - 'Has pubspec.yaml file', folder.getChild('pubspec.yaml').exists));
|
| - buf.writeln('</div>');
|
| -
|
| - buf.writeln('<div class="column one-half">');
|
| - DartSdk sdk = driver?.sourceFactory?.dartSdk;
|
| - AnalysisOptionsImpl sdkOptions = sdk?.context?.analysisOptions;
|
| - if (sdkOptions != null) {
|
| - h3('SDK analysis options');
|
| - p(describe(sdkOptions), raw: true);
|
| -
|
| - if (sdk is FolderBasedDartSdk) {
|
| - p(writeOption('Use summaries', sdk.useSummary), raw: true);
|
| - }
|
| - }
|
| - buf.writeln('</div>');
|
| -
|
| - buf.writeln('</div>');
|
| -
|
| - h3('Lints');
|
| - p(driver.analysisOptions.lintRules.map((l) => l.name).join(', '));
|
| -
|
| - h3('Error processors');
|
| - p(driver.analysisOptions.errorProcessors
|
| - .map((e) => e.description)
|
| - .join(', '));
|
| -
|
| - List<String> priorityFiles = driver.priorityFiles;
|
| - List<String> addedFiles = driver.addedFiles.toList();
|
| - List<String> implicitFiles =
|
| - driver.knownFiles.difference(driver.addedFiles).toList();
|
| - addedFiles.sort();
|
| - implicitFiles.sort();
|
| + final String issuesUrl = 'https://github.com/dart-lang/sdk/issues';
|
| + p(
|
| + 'To file issues or feature requests, see our '
|
| + '<a href="$issuesUrl">bug tracker</a>. When filing an issue, please describe:',
|
| + raw: true,
|
| + );
|
| + ul([
|
| + 'what you were doing',
|
| + 'what occured',
|
| + 'what you think the expected behavior should have been',
|
| + ], (line) => buf.writeln(line));
|
|
|
| - String lenCounter(List list) {
|
| - return '<span class="counter" style="float: right;">${list.length}</span>';
|
| + List<String> ideInfo = [];
|
| + if (server.options.clientId != null) {
|
| + ideInfo.add(server.options.clientId);
|
| }
|
| -
|
| - h3('Context files');
|
| -
|
| - void writeFile(String file) {
|
| - String astPath = '/ast?file=${Uri.encodeQueryComponent(file)}';
|
| - String elementPath = '/element?file=${Uri.encodeQueryComponent(file)}';
|
| -
|
| - buf.write(file);
|
| - buf.write(' (');
|
| - buf.writeln('<a href="$astPath">ast</a>');
|
| - buf.write(' ');
|
| - buf.writeln('<a href="$elementPath">element</a>');
|
| - buf.write(')');
|
| + if (server.options.clientVersion != null) {
|
| + ideInfo.add(server.options.clientVersion);
|
| }
|
| + String ideText = ideInfo.map((str) => '<code>$str</code>').join(', ');
|
|
|
| - h4('Priority files ${lenCounter(priorityFiles)}', raw: true);
|
| - ul(priorityFiles, writeFile, classes: 'scroll-table');
|
| -
|
| - h4('Added files ${lenCounter(addedFiles)}', raw: true);
|
| - ul(addedFiles, writeFile, classes: 'scroll-table');
|
| -
|
| - h4('ImplicitFiles files ${lenCounter(implicitFiles)}', raw: true);
|
| - ul(implicitFiles, writeFile, classes: 'scroll-table');
|
| + p('Other data to include:');
|
| + ul([
|
| + "the IDE you are using and it's version${ideText.isEmpty ? '' : ' ($ideText)'}",
|
| + 'the Dart SDK version (<code>${escape(_sdkVersion)}</code>)',
|
| + 'your operating system (<code>${escape(Platform.operatingSystem)}</code>)',
|
| + ], (line) => buf.writeln(line));
|
|
|
| - SourceFactory sourceFactory = driver.sourceFactory;
|
| - if (sourceFactory is SourceFactoryImpl) {
|
| - h3('Resolvers');
|
| - for (UriResolver resolver in sourceFactory.resolvers) {
|
| - h4(resolver.runtimeType.toString());
|
| - buf.write('<p class="scroll-table">');
|
| - if (resolver is DartUriResolver) {
|
| - DartSdk sdk = resolver.dartSdk;
|
| - buf.write(' (sdk = ');
|
| - buf.write(sdk.runtimeType);
|
| - if (sdk is FolderBasedDartSdk) {
|
| - buf.write(' (path = ');
|
| - buf.write(sdk.directory.path);
|
| - buf.write(')');
|
| - } else if (sdk is EmbedderSdk) {
|
| - buf.write(' (map = ');
|
| - writeMap(sdk.urlMappings);
|
| - buf.write(')');
|
| - }
|
| - buf.write(')');
|
| - } else if (resolver is SdkExtUriResolver) {
|
| - buf.write(' (map = ');
|
| - writeMap(resolver.urlMappings);
|
| - buf.write(')');
|
| - } else if (resolver is PackageMapUriResolver) {
|
| - writeMap(resolver.packageMap);
|
| - }
|
| - buf.write('</p>');
|
| - }
|
| - }
|
| + p('Thanks!');
|
| }
|
| +}
|
|
|
| - String describe(AnalysisOptionsImpl options) {
|
| - StringBuffer b = new StringBuffer();
|
| +class InstrumentationPage extends DiagnosticPageWithNav {
|
| + InstrumentationPage(DiagnosticsSite site)
|
| + : super(site, 'instrumentation', 'Instrumentation',
|
| + description:
|
| + 'Verbose instrumentation data from the analysis server.');
|
|
|
| - b.write(
|
| - writeOption('Analyze function bodies', options.analyzeFunctionBodies));
|
| - b.write(writeOption('Enable asserts in initializer lists',
|
| - options.enableAssertInitializer));
|
| - b.write(writeOption(
|
| - 'Enable strict call checks', options.enableStrictCallChecks));
|
| - b.write(writeOption('Enable super mixins', options.enableSuperMixins));
|
| - b.write(writeOption('Generate dart2js hints', options.dart2jsHint));
|
| - b.write(writeOption(
|
| - 'Generate errors in implicit files', options.generateImplicitErrors));
|
| - b.write(
|
| - writeOption('Generate errors in SDK files', options.generateSdkErrors));
|
| - b.write(writeOption('Generate hints', options.hint));
|
| - b.write(writeOption('Incremental resolution', options.incremental));
|
| - b.write(writeOption(
|
| - 'Incremental resolution with API changes', options.incrementalApi));
|
| - b.write(writeOption('Preserve comments', options.preserveComments));
|
| - b.write(writeOption('Strong mode', options.strongMode));
|
| - b.write(writeOption('Strong mode hints', options.strongModeHints));
|
| + @override
|
| + void generateContent(Map<String, String> params) {
|
| + p(
|
| + 'Instrumentation can be enabled by starting the analysis server with the '
|
| + '<code>--instrumentation-log-file=path/to/file</code> flag.',
|
| + raw: true);
|
|
|
| - return b.toString();
|
| - }
|
| + if (!AnalysisEngine.instance.instrumentationService.isActive) {
|
| + blankslate('Instrumentation not active.');
|
| + return;
|
| + }
|
|
|
| - void writeList<E>(List<E> list) {
|
| - buf.writeln('[${list.join(', ')}]');
|
| - }
|
| + h3('Instrumentation');
|
|
|
| - void writeMap<V>(Map<String, V> map) {
|
| - List<String> keys = map.keys.toList();
|
| - keys.sort();
|
| - int length = keys.length;
|
| - buf.write('{');
|
| - for (int i = 0; i < length; i++) {
|
| - buf.write('<br>');
|
| - String key = keys[i];
|
| - V value = map[key];
|
| - buf.write(key);
|
| - buf.write(' = ');
|
| - if (value is List) {
|
| - writeList(value);
|
| - } else {
|
| - buf.write(value);
|
| - }
|
| - buf.write(',');
|
| - }
|
| - buf.write('<br>}');
|
| + p('Instrumentation active.');
|
| +
|
| + InstrumentationServer instrumentation =
|
| + AnalysisEngine.instance.instrumentationService.instrumentationServer;
|
| + String description = instrumentation.describe;
|
| + HtmlEscape htmlEscape = new HtmlEscape(HtmlEscapeMode.ELEMENT);
|
| + description = htmlEscape.convert(description);
|
| + // Convert http(s): references to hyperlinks.
|
| + final RegExp urlRegExp = new RegExp(r'[http|https]+:\/*(\S+)');
|
| + description = description.replaceAllMapped(urlRegExp, (Match match) {
|
| + return '<a href="${match.group(0)}">${match.group(1)}</a>';
|
| + });
|
| + p(description.replaceAll('\n', '<br>'), raw: true);
|
| }
|
| }
|
|
|
| @@ -826,6 +874,11 @@ class MemoryAndCpuPage extends DiagnosticPageWithNav {
|
| : super(site, 'memory', 'Memory and CPU Usage',
|
| description: 'Memory and CPU usage for the analysis server.');
|
|
|
| + DiagnosticDomainHandler get diagnosticDomain {
|
| + return server.handlers
|
| + .firstWhere((handler) => handler is DiagnosticDomainHandler);
|
| + }
|
| +
|
| @override
|
| void generateContent(Map<String, String> params) {
|
| UsageInfo usage = profiler.getProcessUsageSync(pid);
|
| @@ -838,11 +891,15 @@ class MemoryAndCpuPage extends DiagnosticPageWithNav {
|
| p('Error retreiving the memory and cpu usage information.');
|
| }
|
| }
|
| +}
|
|
|
| - DiagnosticDomainHandler get diagnosticDomain {
|
| - return server.handlers
|
| - .firstWhere((handler) => handler is DiagnosticDomainHandler);
|
| - }
|
| +class NotFoundPage extends DiagnosticPage {
|
| + final String path;
|
| +
|
| + NotFoundPage(Site site, this.path)
|
| + : super(site, '', '404 Not found', description: "'$path' not found.");
|
| +
|
| + void generateContent(Map<String, String> params) {}
|
| }
|
|
|
| class OverlaysPage extends DiagnosticPageWithNav {
|
| @@ -867,56 +924,166 @@ class OverlaysPage extends DiagnosticPageWithNav {
|
| p('<code>${escape(overlayPath)}</code> not found.', raw: true);
|
| }
|
|
|
| - return;
|
| - }
|
| + return;
|
| + }
|
| +
|
| + if (paths.isEmpty) {
|
| + blankslate('No overlays.');
|
| + } else {
|
| + String lenCounter(List list) {
|
| + return '<span class="counter" style="float: right;">${list.length}</span>';
|
| + }
|
| +
|
| + h3('Overlays ${lenCounter(paths)}', raw: true);
|
| + ul(paths, (String overlayPath) {
|
| + String uri = '$path?overlay=${Uri.encodeQueryComponent(overlayPath)}';
|
| + buf.writeln('<a href="$uri">${escape(overlayPath)}</a>');
|
| + });
|
| + }
|
| + }
|
| +}
|
| +
|
| +class PluginsPage extends DiagnosticPageWithNav {
|
| + PluginsPage(DiagnosticsSite site)
|
| + : super(site, 'plugins', 'Plugins', description: 'Plugins in use.');
|
| +
|
| + @override
|
| + void generateContent(Map<String, String> params) {
|
| + h3('Analysis plugins');
|
| + List<PluginInfo> analysisPlugins = server.pluginManager.plugins;
|
| +
|
| + if (analysisPlugins.isEmpty) {
|
| + blankslate('No analysis plugins active.');
|
| + } else {
|
| + ul(analysisPlugins, (PluginInfo p) {
|
| + buf.writeln('${p.data.name} ${p.pluginId} (${p.data.version})');
|
| + });
|
| + }
|
| +
|
| + h3('Analyzer plugins');
|
| + void writePlugin(Plugin plugin) {
|
| + buf.write(plugin.uniqueIdentifier);
|
| + buf.write(' (');
|
| + buf.write(plugin.runtimeType);
|
| + buf.write(')');
|
| + }
|
| +
|
| + List<Plugin> plugins = [
|
| + AnalysisEngine.instance.enginePlugin,
|
| + server.serverPlugin
|
| + ];
|
| + plugins.addAll(server.userDefinedPlugins);
|
| + ul(plugins, writePlugin);
|
| + }
|
| +}
|
| +
|
| +// TODO(devoncarew): Show the last x requests and responses.
|
| +class ProfilePage extends DiagnosticPageWithNav {
|
| + ProfilePage(DiagnosticsSite site)
|
| + : super(site, 'profile', 'Profiling Info',
|
| + description: 'Profiling performance tag data.');
|
| +
|
| + @override
|
| + void generateContent(Map<String, String> params) {
|
| + // prepare sorted tags
|
| + List<PerformanceTag> tags = PerformanceTag.all.toList();
|
| + tags.remove(ServerPerformanceStatistics.idle);
|
| + tags.remove(PerformanceTag.unknown);
|
| + tags.removeWhere((tag) => tag.elapsedMs == 0);
|
| + tags.sort((a, b) => b.elapsedMs - a.elapsedMs);
|
| +
|
| + // draw a pie chart
|
| + String rowData =
|
| + tags.map((tag) => "['${tag.label}', ${tag.elapsedMs}]").join(',');
|
| + buf.writeln(
|
| + '<div id="chart-div" style="width: 700px; height: 300px;"></div>');
|
| + buf.writeln('''
|
| + <script type="text/javascript">
|
| + google.charts.load('current', {'packages':['corechart']});
|
| + google.charts.setOnLoadCallback(drawChart);
|
| +
|
| + function drawChart() {
|
| + var data = new google.visualization.DataTable();
|
| + data.addColumn('string', 'Tag');
|
| + data.addColumn('number', 'Time (ms)');
|
| + data.addRows([$rowData]);
|
| + var options = {'title': 'Performance Tag Data', 'width': 700, 'height': 300};
|
| + var chart = new google.visualization.PieChart(document.getElementById('chart-div'));
|
| + chart.draw(data, options);
|
| + }
|
| + </script>
|
| +''');
|
| +
|
| + // print total time
|
| + int totalTime =
|
| + tags.fold<int>(0, (int a, PerformanceTag tag) => a + tag.elapsedMs);
|
| + p('Total measured time: ${printMilliseconds(totalTime)}');
|
| +
|
| + // write out a table
|
| + void _writeRow(List<String> data, {bool header: false}) {
|
| + buf.write('<tr>');
|
| + if (header) {
|
| + for (String d in data) {
|
| + buf.write('<th>$d</th>');
|
| + }
|
| + } else {
|
| + buf.write('<td>${data[0]}</td>');
|
|
|
| - if (paths.isEmpty) {
|
| - blankslate('No overlays.');
|
| - } else {
|
| - String lenCounter(List list) {
|
| - return '<span class="counter" style="float: right;">${list.length}</span>';
|
| + for (String d in data.sublist(1)) {
|
| + buf.write('<td class="right">$d</td>');
|
| + }
|
| }
|
| + buf.writeln('</tr>');
|
| + }
|
|
|
| - h3('Overlays ${lenCounter(paths)}', raw: true);
|
| - ul(paths, (String overlayPath) {
|
| - String uri = '$path?overlay=${Uri.encodeQueryComponent(overlayPath)}';
|
| - buf.writeln('<a href="$uri">${escape(overlayPath)}</a>');
|
| - });
|
| + buf.write('<table>');
|
| + _writeRow(['Tag name', 'Time (in ms)', 'Percent'], header: true);
|
| + void writeRow(PerformanceTag tag) {
|
| + double percent = tag.elapsedMs / totalTime;
|
| + _writeRow([
|
| + tag.label,
|
| + printMilliseconds(tag.elapsedMs),
|
| + printPercentage(percent)
|
| + ]);
|
| }
|
| +
|
| + tags.forEach(writeRow);
|
| + buf.write('</table>');
|
| }
|
| }
|
|
|
| -class PluginsPage extends DiagnosticPageWithNav {
|
| - PluginsPage(DiagnosticsSite site)
|
| - : super(site, 'plugins', 'Plugins', description: 'Plugins in use.');
|
| +class StatusPage extends DiagnosticPageWithNav {
|
| + StatusPage(DiagnosticsSite site)
|
| + : super(site, 'status', 'Status',
|
| + description:
|
| + 'General status and diagnostics for the analysis server.');
|
|
|
| @override
|
| void generateContent(Map<String, String> params) {
|
| - h3('Analysis plugins');
|
| - List<PluginInfo> analysisPlugins = server.pluginManager.plugins;
|
| + buf.writeln('<div class="columns">');
|
|
|
| - if (analysisPlugins.isEmpty) {
|
| - blankslate('No analysis plugins active.');
|
| - } else {
|
| - ul(analysisPlugins, (PluginInfo p) {
|
| - buf.writeln('${p.data.name} ${p.pluginId} (${p.data.version})');
|
| - });
|
| - }
|
| + buf.writeln('<div class="column one-half">');
|
| + h3('Status');
|
| + buf.writeln(writeOption(
|
| + 'New analysis driver enabled', server.options.enableNewAnalysisDriver));
|
| + buf.writeln(writeOption('Instrumentation enabled',
|
| + AnalysisEngine.instance.instrumentationService.isActive));
|
| + buf.writeln(writeOption('Server process ID', pid));
|
| + buf.writeln('</div>');
|
|
|
| - h3('Analyzer plugins');
|
| - void writePlugin(Plugin plugin) {
|
| - buf.write(plugin.uniqueIdentifier);
|
| - buf.write(' (');
|
| - buf.write(plugin.runtimeType);
|
| - buf.write(')');
|
| - }
|
| + buf.writeln('<div class="column one-half">');
|
| + h3('Versions');
|
| + buf.writeln(writeOption('Analysis server version', AnalysisServer.VERSION));
|
| + buf.writeln(writeOption('Dart SDK', Platform.version));
|
| + buf.writeln('</div>');
|
|
|
| - List<Plugin> plugins = [
|
| - AnalysisEngine.instance.enginePlugin,
|
| - server.serverPlugin
|
| - ];
|
| - plugins.addAll(server.userDefinedPlugins);
|
| - ul(plugins, writePlugin);
|
| + buf.writeln('</div>');
|
| +
|
| + List<String> lines = (site as DiagnosticsSite).lastPrintedLines;
|
| + if (lines.isNotEmpty) {
|
| + h3('Debug output');
|
| + p(lines.join('\n'), style: 'white-space: pre');
|
| + }
|
| }
|
| }
|
|
|
| @@ -961,170 +1128,3 @@ class SubscriptionsPage extends DiagnosticPageWithNav {
|
| });
|
| }
|
| }
|
| -
|
| -class CompletionPage extends DiagnosticPageWithNav {
|
| - CompletionPage(DiagnosticsSite site)
|
| - : super(site, 'completion', 'Code Completion',
|
| - description: 'Latency statistics for code completion.');
|
| -
|
| - @override
|
| - void generateContent(Map<String, String> params) {
|
| - CompletionDomainHandler completionDomain = server.handlers
|
| - .firstWhere((handler) => handler is CompletionDomainHandler);
|
| -
|
| - List<CompletionPerformance> completions =
|
| - completionDomain.performanceList.items.toList();
|
| -
|
| - if (completions.isEmpty) {
|
| - blankslate('No completions recorded.');
|
| - return;
|
| - }
|
| -
|
| - int fastCount =
|
| - completions.where((c) => c.elapsedInMilliseconds <= 100).length;
|
| - p('${completions.length} results; ${printPercentage(fastCount / completions.length)} within 100ms.');
|
| -
|
| - // draw a chart
|
| - buf.writeln(
|
| - '<div id="chart-div" style="width: 700px; height: 300px;"></div>');
|
| - StringBuffer rowData = new StringBuffer();
|
| - for (int i = completions.length - 1; i >= 0; i--) {
|
| - // [' ', 101.5]
|
| - if (rowData.isNotEmpty) {
|
| - rowData.write(',');
|
| - }
|
| - rowData.write("[' ', ${completions[i].elapsedInMilliseconds}]");
|
| - }
|
| - buf.writeln('''
|
| - <script type="text/javascript">
|
| - google.charts.load('current', {'packages':['bar']});
|
| - google.charts.setOnLoadCallback(drawChart);
|
| - function drawChart() {
|
| - var data = google.visualization.arrayToDataTable([
|
| - ['Completions', 'Time'],
|
| - $rowData
|
| - ]);
|
| - var options = { bars: 'vertical', vAxis: {format: 'decimal'}, height: 300 };
|
| - var chart = new google.charts.Bar(document.getElementById('chart-div'));
|
| - chart.draw(data, google.charts.Bar.convertOptions(options));
|
| - }
|
| - </script>
|
| -''');
|
| -
|
| - // emit the data as a table
|
| - buf.writeln('<table>');
|
| - buf.writeln(
|
| - '<tr><th>Time</th><th>Results</th><th>Source</th><th>Snippet</th></tr>');
|
| - for (CompletionPerformance completion in completions) {
|
| - buf.writeln('<tr>'
|
| - '<td class="pre right">${printMilliseconds(completion.elapsedInMilliseconds)}</td>'
|
| - '<td class="right">${completion.suggestionCount}</td>'
|
| - '<td>${escape(completion.source.shortName)}</td>'
|
| - '<td><code>${escape(completion.snippet)}</code></td>'
|
| - '</tr>');
|
| - }
|
| - buf.writeln('</table>');
|
| - }
|
| -}
|
| -
|
| -// TODO(devoncarew): Show the last x requests and responses.
|
| -class CommunicationsPage extends DiagnosticPageWithNav {
|
| - CommunicationsPage(DiagnosticsSite site)
|
| - : super(site, 'communications', 'Communications',
|
| - description:
|
| - 'Latency statistics for analysis server communications.');
|
| -
|
| - @override
|
| - void generateContent(Map<String, String> params) {
|
| - void writeRow(List<String> data, {List<String> classes}) {
|
| - buf.write("<tr>");
|
| - for (int i = 0; i < data.length; i++) {
|
| - String c = classes == null ? null : classes[i];
|
| - if (c != null) {
|
| - buf.write('<td class="$c">${escape(data[i])}</td>');
|
| - } else {
|
| - buf.write('<td>${escape(data[i])}</td>');
|
| - }
|
| - }
|
| - buf.writeln("</tr>");
|
| - }
|
| -
|
| - buf.writeln('<div class="columns">');
|
| -
|
| - ServerPerformance perf = server.performanceAfterStartup;
|
| - if (perf != null) {
|
| - buf.writeln('<div class="column one-half">');
|
| - h3('Current');
|
| -
|
| - int requestCount = perf.requestCount;
|
| - int averageLatency =
|
| - requestCount > 0 ? (perf.requestLatency ~/ requestCount) : 0;
|
| - int maximumLatency = perf.maxLatency;
|
| - double slowRequestPercent =
|
| - requestCount > 0 ? (perf.slowRequestCount / requestCount) : 0.0;
|
| -
|
| - buf.write('<table>');
|
| - writeRow([printInteger(requestCount), 'requests'],
|
| - classes: ["right", null]);
|
| - writeRow([printMilliseconds(averageLatency), 'average latency'],
|
| - classes: ["right", null]);
|
| - writeRow([printMilliseconds(maximumLatency), 'maximum latency'],
|
| - classes: ["right", null]);
|
| - writeRow([printPercentage(slowRequestPercent), '> 150 ms latency'],
|
| - classes: ["right", null]);
|
| - buf.write('</table>');
|
| -
|
| - String time = server.uptime.toString();
|
| - if (time.contains('.')) {
|
| - time = time.substring(0, time.indexOf('.'));
|
| - }
|
| - buf.writeln(writeOption('Uptime', time));
|
| -
|
| - buf.write('</div>');
|
| - }
|
| -
|
| - buf.writeln('<div class="column one-half">');
|
| - h3('Startup');
|
| - perf = server.performanceDuringStartup;
|
| -
|
| - int requestCount = perf.requestCount;
|
| - int averageLatency =
|
| - requestCount > 0 ? (perf.requestLatency ~/ requestCount) : 0;
|
| - int maximumLatency = perf.maxLatency;
|
| - double slowRequestPercent =
|
| - requestCount > 0 ? (perf.slowRequestCount / requestCount) : 0.0;
|
| -
|
| - buf.write('<table>');
|
| - writeRow([printInteger(requestCount), 'requests'],
|
| - classes: ["right", null]);
|
| - writeRow([printMilliseconds(averageLatency), 'average latency'],
|
| - classes: ["right", null]);
|
| - writeRow([printMilliseconds(maximumLatency), 'maximum latency'],
|
| - classes: ["right", null]);
|
| - writeRow([printPercentage(slowRequestPercent), '> 150 ms latency'],
|
| - classes: ["right", null]);
|
| - buf.write('</table>');
|
| -
|
| - if (server.performanceAfterStartup != null) {
|
| - int startupTime =
|
| - server.performanceAfterStartup.startTime - perf.startTime;
|
| - buf.writeln(
|
| - writeOption('Initial analysis time', printMilliseconds(startupTime)));
|
| - }
|
| - buf.write('</div>');
|
| -
|
| - buf.write('</div>');
|
| - }
|
| -}
|
| -
|
| -String writeOption(String name, dynamic value) {
|
| - return '$name: <code>$value</code><br> ';
|
| -}
|
| -
|
| -String get _sdkVersion {
|
| - String version = Platform.version;
|
| - if (version.contains(' ')) {
|
| - version = version.substring(0, version.indexOf(' '));
|
| - }
|
| - return version;
|
| -}
|
|
|