@@ -275,34 +275,38 @@ def _build_spans_tree(self, trace_messages: list[TraceMessage]):
275275 spans_tree = self .query_one ("#spans-tree" , Tree )
276276 root = spans_tree .root
277277
278- # Group spans by parent relationship
279- root_spans = []
280- child_spans : Dict [str , List [TraceMessage ]] = {}
281-
282- for trace_msg in trace_messages :
283- if not trace_msg .parent_span_id :
284- root_spans .append (trace_msg )
285- else :
286- if trace_msg .parent_span_id not in child_spans :
287- child_spans [trace_msg .parent_span_id ] = []
288- child_spans [trace_msg .parent_span_id ].append (trace_msg )
278+ # Filter out spans without parents (artificial root spans)
279+ spans_by_id = {
280+ msg .span_id : msg for msg in trace_messages if msg .parent_span_id is not None
281+ }
289282
290- # Build tree recursively
283+ # Build parent-to-children mapping once upfront
284+ children_by_parent : Dict [str , List [TraceMessage ]] = {}
285+ for msg in spans_by_id .values ():
286+ if msg .parent_span_id :
287+ if msg .parent_span_id not in children_by_parent :
288+ children_by_parent [msg .parent_span_id ] = []
289+ children_by_parent [msg .parent_span_id ].append (msg )
290+
291+ # Find root spans (parent doesn't exist in our filtered data)
292+ root_spans = [
293+ msg
294+ for msg in trace_messages
295+ if msg .parent_span_id and msg .parent_span_id not in spans_by_id
296+ ]
297+
298+ # Build tree recursively for each root span
291299 for root_span in sorted (root_spans , key = lambda x : x .timestamp ):
292- if root_span .span_id in child_spans :
293- for child in sorted (
294- child_spans [root_span .span_id ], key = lambda x : x .timestamp
295- ):
296- self ._add_span_node (root , child , child_spans )
300+ self ._add_span_with_children (root , root_span , children_by_parent )
297301
298- def _add_span_node (
302+ def _add_span_with_children (
299303 self ,
300304 parent_node : TreeNode [str ],
301305 trace_msg : TraceMessage ,
302- child_spans : Dict [str , List [TraceMessage ]],
306+ children_by_parent : Dict [str , List [TraceMessage ]],
303307 ):
304- """Recursively add span nodes to the tree ."""
305- # Create display label for the span
308+ """Recursively add a span and all its children ."""
309+ # Create the node for this span
306310 color_map = {
307311 "started" : "🔵" ,
308312 "running" : "🟡" ,
@@ -311,26 +315,20 @@ def _add_span_node(
311315 "error" : "🔴" ,
312316 }
313317 status_icon = color_map .get (trace_msg .status .lower (), "⚪" )
314-
315318 duration_str = (
316319 f" ({ trace_msg .duration_ms :.1f} ms)" if trace_msg .duration_ms else ""
317320 )
318321 label = f"{ status_icon } { trace_msg .span_name } { duration_str } "
319322
320- # Add node to tree
321323 node = parent_node .add (label )
322- node .data = trace_msg .span_id # Store span_id for reference
324+ node .data = trace_msg .span_id
323325 self .span_tree_nodes [trace_msg .span_id ] = node
324-
325326 node .expand ()
326327
327- # Add child spans (sorted by timestamp)
328- if trace_msg .span_id in child_spans :
329- sorted_children = sorted (
330- child_spans [trace_msg .span_id ], key = lambda x : x .timestamp
331- )
332- for child_span in sorted_children :
333- self ._add_span_node (node , child_span , child_spans )
328+ # Get children from prebuilt mapping - O(1) lookup
329+ children = children_by_parent .get (trace_msg .span_id , [])
330+ for child in sorted (children , key = lambda x : x .timestamp ):
331+ self ._add_span_with_children (node , child , children_by_parent )
334332
335333 def on_tree_node_selected (self , event : Tree .NodeSelected [str ]) -> None :
336334 """Handle span selection in the tree."""
0 commit comments