23bee1698f
Change-Id: I2087c3c9d25cdf3266142ba57bb8dbc57bc3fc46 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/194002 Commit-Queue: Brian Wilkerson <brianwilkerson@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
243 lines
8.6 KiB
Dart
243 lines
8.6 KiB
Dart
// 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;
|
|
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>');
|
|
});
|
|
}
|
|
}
|
|
}
|