| // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 
 | // for details. All rights reserved. Use of this source code is governed by a | 
 | // BSD-style license that can be found in the LICENSE file. | 
 |  | 
 | import '../log/log.dart'; | 
 | import 'page_writer.dart'; | 
 |  | 
 | /// A page writer that will produce the page containing statistics about an | 
 | /// instrumentation log. | 
 | class StatsPage extends PageWriter { | 
 |   /// The instrumentation log to be written. | 
 |   final InstrumentationLog log; | 
 |  | 
 |   /// A table mapping the kinds of entries in the log to the number of each | 
 |   /// kind. | 
 |   final Map<String, int> entryCounts = <String, int>{}; | 
 |  | 
 |   /// The number of responses that returned an error. | 
 |   int errorCount = 0; | 
 |  | 
 |   /// The number of responses from each plugin that returned an error. | 
 |   Map<String, int> pluginErrorCount = <String, int>{}; | 
 |  | 
 |   /// A table mapping request method names to a list of the latencies associated | 
 |   /// with those requests, where the latency is defined to be the time between | 
 |   /// when the request was sent by the client and when the server started | 
 |   /// processing the request. | 
 |   final Map<String, List<int>> latencyData = <String, List<int>>{}; | 
 |  | 
 |   /// A table mapping request method names to a list of the latencies associated | 
 |   /// with those requests, where the latency is defined to be the time between | 
 |   /// when the request was sent by the server and when the plugin sent a | 
 |   /// response. | 
 |   final Map<String, Map<String, List<int>>> pluginResponseData = | 
 |       <String, Map<String, List<int>>>{}; | 
 |  | 
 |   /// A list of the number of milliseconds between a completion request and the | 
 |   /// first event for that request. | 
 |   final List<int> completionResponseTimes = <int>[]; | 
 |  | 
 |   /// Initialize a newly created page writer to write information about the | 
 |   /// given instrumentation [log]. | 
 |   StatsPage(this.log) { | 
 |     _processEntries(log.logEntries); | 
 |   } | 
 |  | 
 |   @override | 
 |   void writeBody(StringSink sink) { | 
 |     writeMenu(sink); | 
 |     writeTwoColumns( | 
 |         sink, 'leftColumn', _writeLeftColumn, 'rightColumn', _writeRightColumn); | 
 |   } | 
 |  | 
 |   /// Write the content of the style sheet (without the 'script' tag) for the | 
 |   /// page to the given [sink]. | 
 |   @override | 
 |   void writeStyleSheet(StringSink sink) { | 
 |     super.writeStyleSheet(sink); | 
 |     writeTwoColumnStyles(sink, 'leftColumn', 'rightColumn'); | 
 |   } | 
 |  | 
 |   /// Return the mean of the values in the given list of [values]. | 
 |   int _mean(List<int> values) { | 
 |     var sum = values.fold(0, (int sum, int latency) => sum + latency); | 
 |     return sum ~/ values.length; | 
 |   } | 
 |  | 
 |   /// Return a table mapping the kinds of the given [entries] to the number of | 
 |   /// each kind. | 
 |   void _processEntries(List<LogEntry> entries) { | 
 |     void increment<K>(Map<K, int> map, K key) { | 
 |       map[key] = (map[key] ?? 0) + 1; | 
 |     } | 
 |  | 
 |     for (var entry in entries) { | 
 |       var kind = entry.kind; | 
 |       increment(entryCounts, kind); | 
 |       if (entry is ResponseEntry) { | 
 |         if (entry.result('error') != null) { | 
 |           errorCount++; | 
 |         } | 
 |       } else if (entry is RequestEntry) { | 
 |         var method = entry.method; | 
 |         var latency = entry.timeStamp - entry.clientRequestTime; | 
 |         if (method != null) { | 
 |           latencyData.putIfAbsent(method, () => <int>[]).add(latency); | 
 |         } | 
 |         if (method == 'completion.getSuggestions') { | 
 |           var response = log.responseFor(entry); | 
 |           if (response != null) { | 
 |             var id = response.result('id'); | 
 |             if (id is String) { | 
 |               var events = log.completionEventsWithId(id); | 
 |               if (events != null && events.isNotEmpty) { | 
 |                 completionResponseTimes | 
 |                     .add(events[0].timeStamp - entry.timeStamp); | 
 |               } | 
 |             } | 
 |           } | 
 |         } | 
 |       } else if (entry is PluginResponseEntry) { | 
 |         if (entry.result('error') != null) { | 
 |           var count = pluginErrorCount[entry.pluginId] ?? 0; | 
 |           pluginErrorCount[entry.pluginId] = count + 1; | 
 |         } | 
 |       } else if (entry is PluginRequestEntry) { | 
 |         var response = log.pluginResponseFor(entry)!; | 
 |         var responseTime = response.timeStamp - entry.timeStamp; | 
 |         var pluginData = pluginResponseData.putIfAbsent( | 
 |             entry.pluginId, () => <String, List<int>>{}); | 
 |         pluginData.putIfAbsent(entry.method, () => <int>[]).add(responseTime); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   void _writeLeftColumn(StringSink sink) { | 
 |     var filePaths = log.logFilePaths; | 
 |     var entries = log.logEntries; | 
 |     var startDate = entries[0].toTime; | 
 |     var endDate = entries[entries.length - 1].toTime; | 
 |     var duration = endDate.difference(startDate); | 
 |     var entryKinds = entryCounts.keys.toList()..sort(); | 
 |  | 
 |     sink.writeln('<h3>General</h3>'); | 
 |     sink.writeln('<p>'); | 
 |     if (filePaths.length == 1) { | 
 |       sink.write('<span class="label">Log file:</span> '); | 
 |       sink.write(filePaths[0]); | 
 |     } else { | 
 |       sink.write('<span class="label">Log files:</span> '); | 
 |       var needsSeparator = false; | 
 |       for (var path in filePaths) { | 
 |         if (needsSeparator) { | 
 |           sink.write(', '); | 
 |         } else { | 
 |           needsSeparator = true; | 
 |         } | 
 |         sink.write(path); | 
 |       } | 
 |     } | 
 |     sink.writeln('<br>'); | 
 |     sink.write('<span class="label">Start time:</span> '); | 
 |     writeDate(sink, startDate); | 
 |     sink.writeln('<br>'); | 
 |     sink.write('<span class="label">End time:</span> '); | 
 |     writeDate(sink, endDate); | 
 |     sink.writeln('<br>'); | 
 |     sink.write('<span class="label">Duration:</span> '); | 
 |     sink.write(duration.toString()); | 
 |     sink.writeln('</p>'); | 
 |  | 
 |     sink.writeln('<h3>Entries</h3>'); | 
 |     sink.write('<p>'); | 
 |     sink.write('<span class="label">Number of entries:</span> '); | 
 |     sink.write(entries.length); | 
 |     sink.writeln('</p>'); | 
 |     sink.write('<p>'); | 
 |     sink.write('<span class="label">Error count:</span> '); | 
 |     sink.write(errorCount); | 
 |     sink.writeln('</p>'); | 
 |     pluginErrorCount.forEach((String pluginId, int count) { | 
 |       sink.write('<p>'); | 
 |       sink.write('<span class="label">Errors from $pluginId:</span> '); | 
 |       sink.write(count); | 
 |       sink.writeln('</p>'); | 
 |     }); | 
 |     sink.writeln('<table>'); | 
 |     sink.writeln('<tr><th>count</th><th>kind</th></tr>'); | 
 |     for (var kind in entryKinds) { | 
 |       sink.write('<tr><td class="int">'); | 
 |       sink.write(entryCounts[kind]); | 
 |       sink.write('</td><td>'); | 
 |       sink.write(kind); | 
 |       sink.writeln('</td></tr>'); | 
 |     } | 
 |     sink.write('<tr><td class="int">'); | 
 |     sink.write(entries.length); | 
 |     sink.writeln('</td><td>Total</td></tr>'); | 
 |     sink.writeln('</table>'); | 
 |   } | 
 |  | 
 |   void _writeRightColumn(StringSink sink) { | 
 |     completionResponseTimes.sort(); | 
 |  | 
 |     sink.writeln('<h3>Latency</h3>'); | 
 |     sink.write('<p>'); | 
 |     sink.write('<span class="label">Latency by method</span>'); | 
 |     sink.writeln('</p>'); | 
 |     sink.writeln('<table>'); | 
 |     sink.writeln( | 
 |         '<tr><th>min</th><th>mean</th><th>max</th><th>method</th></tr>'); | 
 |     var methodNames = latencyData.keys.toList()..sort(); | 
 |     for (var method in methodNames) { | 
 |       var latencies = latencyData[method]!..sort(); | 
 |       // TODO(brianwilkerson): Add a spark-line distribution graph. | 
 |       sink.write('<tr><td class="int">'); | 
 |       sink.write(latencies[0]); | 
 |       sink.write('</td><td class="int">'); | 
 |       sink.write(_mean(latencies)); | 
 |       sink.write('</td><td class="int">'); | 
 |       sink.write(latencies[latencies.length - 1]); | 
 |       sink.write('</td><td>'); | 
 |       sink.write(method); | 
 |       sink.writeln('</td></tr>'); | 
 |     } | 
 |     sink.writeln('</table>'); | 
 |  | 
 |     sink.writeln('<h3>Completion</h3>'); | 
 |     sink.write('<p>'); | 
 |     sink.write('<span class="label">Time to first notification:</span> '); | 
 |     sink.write(completionResponseTimes[0]); | 
 |     sink.write(', '); | 
 |     sink.write(_mean(completionResponseTimes)); | 
 |     sink.write(', '); | 
 |     sink.write(completionResponseTimes[completionResponseTimes.length - 1]); | 
 |     sink.writeln('</p>'); | 
 |  | 
 |     if (pluginResponseData.isNotEmpty) { | 
 |       sink.writeln('<h3>Plugin response times</h3>'); | 
 |       pluginResponseData | 
 |           .forEach((String pluginId, Map<String, List<int>> responseData) { | 
 |         sink.write('<p>'); | 
 |         sink.write(pluginId); | 
 |         sink.writeln('</p>'); | 
 |         sink.writeln('<table>'); | 
 |         var methodNames = responseData.keys.toList()..sort(); | 
 |         for (var method in methodNames) { | 
 |           var responseTimes = responseData[method]!..sort(); | 
 |           // TODO(brianwilkerson): Add a spark-line distribution graph. | 
 |           sink.write('<tr><td class="int">'); | 
 |           sink.write(responseTimes[0]); | 
 |           sink.write('</td><td class="int">'); | 
 |           sink.write(_mean(responseTimes)); | 
 |           sink.write('</td><td class="int">'); | 
 |           sink.write(responseTimes[responseTimes.length - 1]); | 
 |           sink.write('</td><td>'); | 
 |           sink.write(method); | 
 |           sink.writeln('</td></tr>'); | 
 |         } | 
 |         sink.writeln('</table>'); | 
 |       }); | 
 |     } | 
 |   } | 
 | } |