Index: pkg/analysis_server/tool/instrumentation/log/log.dart |
diff --git a/pkg/analysis_server/tool/instrumentation/log/log.dart b/pkg/analysis_server/tool/instrumentation/log/log.dart |
index 7042fc467dd5e13f07f925411b9e1cd82f0d087b..2378c27a33454801dad5055e845af436dd99272e 100644 |
--- a/pkg/analysis_server/tool/instrumentation/log/log.dart |
+++ b/pkg/analysis_server/tool/instrumentation/log/log.dart |
@@ -5,14 +5,83 @@ |
/** |
* A representation of the contents of an instrumentation log. |
*/ |
-library analysis_server.tool.instrumentation.log; |
+library analysis_server.tool.instrumentation.log.log; |
-import 'dart:collection'; |
import 'dart:convert'; |
import 'package:analyzer/instrumentation/instrumentation.dart'; |
/** |
+ * A boolean-valued function of one argument. |
+ */ |
+typedef bool Predicate<T>(T value); |
+ |
+/** |
+ * A description of a group of log entries. |
+ */ |
+class EntryGroup { |
+ /** |
+ * A list of all of the instances of this class. |
+ */ |
+ static final List<EntryGroup> groups = <EntryGroup>[ |
+ new EntryGroup._( |
+ 'nonTask', 'Non-task', (LogEntry entry) => entry is! TaskEntry), |
+ new EntryGroup._( |
+ 'errors', |
+ 'Errors', |
+ (LogEntry entry) => |
+ entry is ErrorEntry || |
+ entry is ExceptionEntry || |
+ (entry is NotificationEntry && entry.isServerError)), |
+ new EntryGroup._('malformed', 'Malformed', |
+ (LogEntry entry) => entry is MalformedLogEntry), |
+ new EntryGroup._('all', 'All', (LogEntry entry) => true), |
+ ]; |
+ |
+ /** |
+ * The unique id of the group. |
+ */ |
+ final String id; |
+ |
+ /** |
+ * The human-readable name of the group. |
+ */ |
+ final String name; |
+ |
+ /** |
+ * The filter used to determine which entries belong to the group. The filter |
+ * should return `true` for members and `false` for non-members. |
+ */ |
+ final Predicate<LogEntry> filter; |
+ |
+ /** |
+ * Initialize a newly created entry group with the given state. |
+ */ |
+ EntryGroup._(this.id, this.name, this.filter); |
+ |
+ /** |
+ * Given a list of [entries], return all of the entries in the list that are |
+ * members of this group. |
+ */ |
+ List<LogEntry> computeMembers(List<LogEntry> entries) { |
+ return entries.where(filter).toList(); |
+ } |
+ |
+ /** |
+ * Return the entry group with the given [id], or `null` if there is no group |
+ * with the given id. |
+ */ |
+ static EntryGroup withId(String id) { |
+ for (EntryGroup group in groups) { |
+ if (group.id == id) { |
+ return group; |
+ } |
+ } |
+ return null; |
+ } |
+} |
+ |
+/** |
* A range of log entries, represented by the index of the first and last |
* entries in the range. |
*/ |
@@ -107,34 +176,33 @@ class InstrumentationLog { |
List<LogEntry> logEntries; |
/** |
- * The entries in the instrumentation log that are not instances of |
- * [TaskEntry]. |
+ * A table mapping the entry groups that have been computed to the list of |
+ * entries in that group. |
*/ |
- List<LogEntry> nonTaskEntries; |
+ Map<EntryGroup, List<LogEntry>> entryGroups = <EntryGroup, List<LogEntry>>{}; |
/** |
* A table mapping entries that are paired with another entry to the entry |
* with which they are paired. |
*/ |
- Map<LogEntry, LogEntry> _pairedEntries = new HashMap<LogEntry, LogEntry>(); |
+ Map<LogEntry, LogEntry> _pairedEntries = <LogEntry, LogEntry>{}; |
/** |
* A table mapping the id's of requests to the entry representing the request. |
*/ |
- Map<String, RequestEntry> _requestMap = new HashMap<String, RequestEntry>(); |
+ Map<String, RequestEntry> _requestMap = <String, RequestEntry>{}; |
/** |
* A table mapping the id's of responses to the entry representing the |
* response. |
*/ |
- Map<String, ResponseEntry> _responseMap = |
- new HashMap<String, ResponseEntry>(); |
+ Map<String, ResponseEntry> _responseMap = <String, ResponseEntry>{}; |
/** |
* A table mapping the ids of completion events to the events with those ids. |
*/ |
Map<String, List<NotificationEntry>> _completionMap = |
- new HashMap<String, List<NotificationEntry>>(); |
+ <String, List<NotificationEntry>>{}; |
/** |
* The ranges of entries that are between analysis start and analysis end |
@@ -158,6 +226,12 @@ class InstrumentationLog { |
_completionMap[id]; |
/** |
+ * Return the log entries that are contained in the given [group]. |
+ */ |
+ List<LogEntry> entriesInGroup(EntryGroup group) => |
+ entryGroups.putIfAbsent(group, () => group.computeMembers(logEntries)); |
+ |
+ /** |
* Return the entry that is paired with the given [entry], or `null` if there |
* is no entry paired with it. |
*/ |
@@ -181,7 +255,7 @@ class InstrumentationLog { |
*/ |
List<TaskEntry> taskEntriesFor(int startIndex) { |
List<TaskEntry> taskEntries = <TaskEntry>[]; |
- NotificationEntry startEntry = nonTaskEntries[startIndex]; |
+ NotificationEntry startEntry = logEntries[startIndex]; |
LogEntry endEntry = pairedEntry(startEntry); |
int lastIndex = endEntry == null ? logEntries.length : endEntry.index; |
for (int i = startEntry.index + 1; i < lastIndex; i++) { |
@@ -194,6 +268,18 @@ class InstrumentationLog { |
} |
/** |
+ * Return `true` if the given [logContent] appears to be from session data. |
+ */ |
+ bool _isSessionData(List<String> logContent) { |
+ if (logContent.length < 2) { |
+ return false; |
+ } |
+ String firstLine = logContent[0]; |
+ return firstLine.startsWith('-----') && logContent[1].startsWith('~') || |
+ firstLine.startsWith('~'); |
+ } |
+ |
+ /** |
* Merge any multi-line entries into a single line so that every element in |
* the given [logContent] is a single entry. |
*/ |
@@ -233,9 +319,18 @@ class InstrumentationLog { |
* Parse the given [logContent] into a list of log entries. |
*/ |
void _parseLogContent(List<String> logContent) { |
- _mergeEntries(logContent); |
+ if (_isSessionData(logContent)) { |
+ if (logContent[0].startsWith('-----')) { |
+ logContent.removeAt(0); |
+ } |
+ int lastIndex = logContent.length - 1; |
+ if (logContent[lastIndex].startsWith('extraction complete')) { |
+ logContent.removeAt(lastIndex); |
+ } |
+ } else { |
+ _mergeEntries(logContent); |
+ } |
logEntries = <LogEntry>[]; |
- nonTaskEntries = <LogEntry>[]; |
analysisRanges = <EntryRange>[]; |
NotificationEntry analysisStartEntry = null; |
int analysisStartIndex = -1; |
@@ -244,9 +339,6 @@ class InstrumentationLog { |
LogEntry entry = new LogEntry.from(logEntries.length, line); |
if (entry != null) { |
logEntries.add(entry); |
- if (entry is! TaskEntry) { |
- nonTaskEntries.add(entry); |
- } |
if (entry is RequestEntry) { |
_requestMap[entry.id] = entry; |
} else if (entry is ResponseEntry) { |
@@ -439,6 +531,7 @@ abstract class LogEntry { |
'Err': 'Error', |
'Ex': 'Exception', |
'Log': 'Log message', |
+ 'Mal': 'Malformed entry', |
'Noti': 'Notification', |
'Read': 'Read file', |
'Req': 'Request', |
@@ -479,47 +572,54 @@ abstract class LogEntry { |
if (entry.isEmpty) { |
return null; |
} |
- List<String> components = _parseComponents(entry); |
- int timeStamp; |
try { |
- timeStamp = int.parse(components[0]); |
- } catch (exception) { |
- print('Invalid time stamp in "${components[0]}"; entry = "$entry"'); |
- return null; |
- } |
- String entryKind = components[1]; |
- if (entryKind == InstrumentationService.TAG_ANALYSIS_TASK) { |
- return new TaskEntry(index, timeStamp, components[2], components[3]); |
- } else if (entryKind == InstrumentationService.TAG_ERROR) { |
- return new ErrorEntry(index, timeStamp, entryKind, components.sublist(2)); |
- } else if (entryKind == InstrumentationService.TAG_EXCEPTION) { |
- return new ExceptionEntry( |
+ List<String> components = _parseComponents(entry); |
+ int timeStamp; |
+ String component = components[0]; |
+ if (component.startsWith('~')) { |
+ component = component.substring(1); |
+ } |
+ timeStamp = int.parse(component); |
+ String entryKind = components[1]; |
+ if (entryKind == InstrumentationService.TAG_ANALYSIS_TASK) { |
+ return new TaskEntry(index, timeStamp, components[2], components[3]); |
+ } else if (entryKind == InstrumentationService.TAG_ERROR) { |
+ return new ErrorEntry( |
+ index, timeStamp, entryKind, components.sublist(2)); |
+ } else if (entryKind == InstrumentationService.TAG_EXCEPTION) { |
+ return new ExceptionEntry( |
+ index, timeStamp, entryKind, components.sublist(2)); |
+ } else if (entryKind == InstrumentationService.TAG_FILE_READ) { |
+ // Fall through |
+ } else if (entryKind == InstrumentationService.TAG_LOG_ENTRY) { |
+ // Fall through |
+ } else if (entryKind == InstrumentationService.TAG_NOTIFICATION) { |
+ Map requestData = JSON.decode(components[2]); |
+ return new NotificationEntry(index, timeStamp, requestData); |
+ } else if (entryKind == InstrumentationService.TAG_PERFORMANCE) { |
+ // Fall through |
+ } else if (entryKind == InstrumentationService.TAG_REQUEST) { |
+ Map requestData = JSON.decode(components[2]); |
+ return new RequestEntry(index, timeStamp, requestData); |
+ } else if (entryKind == InstrumentationService.TAG_RESPONSE) { |
+ Map responseData = JSON.decode(components[2]); |
+ return new ResponseEntry(index, timeStamp, responseData); |
+ } else if (entryKind == InstrumentationService.TAG_SUBPROCESS_START) { |
+ // Fall through |
+ } else if (entryKind == InstrumentationService.TAG_SUBPROCESS_RESULT) { |
+ // Fall through |
+ } else if (entryKind == InstrumentationService.TAG_VERSION) { |
+ // Fall through |
+ } else if (entryKind == InstrumentationService.TAG_WATCH_EVENT) { |
+ // Fall through |
+ } |
+ return new GenericEntry( |
index, timeStamp, entryKind, components.sublist(2)); |
- } else if (entryKind == InstrumentationService.TAG_FILE_READ) { |
- // Fall through |
- } else if (entryKind == InstrumentationService.TAG_LOG_ENTRY) { |
- // Fall through |
- } else if (entryKind == InstrumentationService.TAG_NOTIFICATION) { |
- Map requestData = JSON.decode(components[2]); |
- return new NotificationEntry(index, timeStamp, requestData); |
- } else if (entryKind == InstrumentationService.TAG_PERFORMANCE) { |
- // Fall through |
- } else if (entryKind == InstrumentationService.TAG_REQUEST) { |
- Map requestData = JSON.decode(components[2]); |
- return new RequestEntry(index, timeStamp, requestData); |
- } else if (entryKind == InstrumentationService.TAG_RESPONSE) { |
- Map responseData = JSON.decode(components[2]); |
- return new ResponseEntry(index, timeStamp, responseData); |
- } else if (entryKind == InstrumentationService.TAG_SUBPROCESS_START) { |
- // Fall through |
- } else if (entryKind == InstrumentationService.TAG_SUBPROCESS_RESULT) { |
- // Fall through |
- } else if (entryKind == InstrumentationService.TAG_VERSION) { |
- // Fall through |
- } else if (entryKind == InstrumentationService.TAG_WATCH_EVENT) { |
- // Fall through |
+ } catch (exception) { |
+ LogEntry logEntry = new MalformedLogEntry(index, entry); |
+ logEntry.recordProblem(exception.toString()); |
+ return logEntry; |
} |
- return new GenericEntry(index, timeStamp, entryKind, components.sublist(2)); |
} |
/** |
@@ -608,6 +708,25 @@ abstract class LogEntry { |
} |
/** |
+ * A representation of a malformed log entry. |
+ */ |
+class MalformedLogEntry extends LogEntry { |
+ final String entry; |
+ |
+ MalformedLogEntry(int index, this.entry) : super(index, -1); |
+ |
+ @override |
+ String get kind => 'Mal'; |
+ |
+ @override |
+ void _appendDetails(StringBuffer buffer) { |
+ super._appendDetails(buffer); |
+ buffer.write(entry); |
+ buffer.write('<br>'); |
+ } |
+} |
+ |
+/** |
* A log entry representing a notification that was sent from the server to the |
* client. |
*/ |
@@ -625,6 +744,11 @@ class NotificationEntry extends JsonBasedEntry { |
String get event => data['event']; |
/** |
+ * Return `true` if this is a server error notification. |
+ */ |
+ bool get isServerError => event == 'server.error'; |
+ |
+ /** |
* Return `true` if this is a server status notification. |
*/ |
bool get isServerStatus => event == 'server.status'; |