computeSpans function
Reconstructs TraceSpans from raw TraceEvents.
Implementation
List<TraceSpan> computeSpans(List<TraceEvent> events) {
final sorted = List<TraceEvent>.from(events)
..sort((a, b) => a.timestamp.compareTo(b.timestamp));
final absoluteMaxTimestamp = sorted.isEmpty ? 0 : sorted.last.timestamp;
final List<TraceSpan> spans = [];
final Map<int, List<_StackEntry>> activeStacksByTid = {};
for (final event in sorted) {
final tid = event.tid;
activeStacksByTid.putIfAbsent(tid, () => []);
final stack = activeStacksByTid[tid]!;
if (event.phase == 'B') {
stack.add(_StackEntry(event, stack.length));
} else if (event.phase == 'E') {
int matchIdx = -1;
for (int i = stack.length - 1; i >= 0; i--) {
if (stack[i].event.name == event.name) {
matchIdx = i;
break;
}
}
if (matchIdx != -1) {
// Pop orphaned children above the matched parent
while (stack.length > matchIdx + 1) {
final orphanEntry = stack.removeLast();
final orphanEvent = orphanEntry.event;
spans.add(
TraceSpan(
name: orphanEvent.name,
category: orphanEvent.category,
startUs: orphanEvent.timestamp,
endUs: event.timestamp, // Auto-closed at parent's end
depth: orphanEntry.depth,
args: orphanEvent.args,
),
);
}
// Pop the actual matched parent
final beginEntry = stack.removeLast();
final begin = beginEntry.event;
spans.add(
TraceSpan(
name: begin.name,
category: begin.category,
startUs: begin.timestamp,
endUs: event.timestamp,
depth: beginEntry.depth,
args: begin.args,
),
);
}
} else if (event.phase == 'i') {
spans.add(
TraceSpan(
name: event.name,
category: event.category,
startUs: event.timestamp,
endUs: event.timestamp + 1,
depth: stack.length,
args: event.args,
),
);
}
}
for (final tid in activeStacksByTid.keys) {
for (final entry in activeStacksByTid[tid]!) {
final begin = entry.event;
spans.add(
TraceSpan(
name: begin.name,
category: begin.category,
startUs: begin.timestamp,
endUs:
absoluteMaxTimestamp, // Indicates missing 'E' phase is clamped to trace end
depth: entry.depth,
args: begin.args,
),
);
}
}
return spans;
}