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; |
-} |