Skip to content
This repository was archived by the owner on Mar 4, 2026. It is now read-only.

Commit 0743802

Browse files
committed
span registry
1 parent 0715d68 commit 0743802

1 file changed

Lines changed: 116 additions & 100 deletions

File tree

src/uipath/core/tracing/manager.py

Lines changed: 116 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Tracing manager for handling tracer implementations and function registry."""
22

33
import logging
4-
from typing import Any, Callable, List, Optional
4+
from typing import Any, Callable, Dict, List, Optional
55

66
from opentelemetry import context, trace
77
from opentelemetry.sdk.trace import ReadableSpan
@@ -10,6 +10,84 @@
1010
logger = logging.getLogger(__name__)
1111

1212

13+
class SpanRegistry:
14+
"""Registry to track all spans and their parent relationships."""
15+
16+
def __init__(self):
17+
self._spans: Dict[int, ReadableSpan] = {} # span_id -> span
18+
self._parent_map: Dict[int, Optional[int]] = {} # span_id -> parent_id
19+
20+
def register_span(self, span: ReadableSpan) -> None:
21+
"""Register a span and its parent relationship."""
22+
span_id = span.get_span_context().span_id
23+
parent_id = span.parent.span_id if span.parent else None
24+
25+
self._spans[span_id] = span
26+
self._parent_map[span_id] = parent_id
27+
28+
parent_str = f"{parent_id:016x}" if parent_id is not None else "None"
29+
logger.info(f"Registered span: {span.name} (id: {span_id:016x}, parent: {parent_str})")
30+
31+
def get_span(self, span_id: int) -> Optional[ReadableSpan]:
32+
"""Get a span by ID."""
33+
return self._spans.get(span_id)
34+
35+
def get_parent_id(self, span_id: int) -> Optional[int]:
36+
"""Get the parent ID of a span."""
37+
return self._parent_map.get(span_id)
38+
39+
def calculate_depth(self, span_id: int) -> int:
40+
"""Calculate the depth of a span in the hierarchy."""
41+
depth = 0
42+
current_id = span_id
43+
visited = set()
44+
45+
while current_id is not None and current_id not in visited:
46+
visited.add(current_id)
47+
parent_id = self._parent_map.get(current_id)
48+
if parent_id is None:
49+
break
50+
depth += 1
51+
current_id = parent_id
52+
53+
return depth
54+
55+
def is_ancestor(self, ancestor_id: int, descendant_id: int) -> bool:
56+
"""Check if ancestor_id is an ancestor of descendant_id."""
57+
current_id = descendant_id
58+
visited = set()
59+
60+
while current_id is not None and current_id not in visited:
61+
if current_id == ancestor_id:
62+
return True
63+
visited.add(current_id)
64+
current_id = self._parent_map.get(current_id)
65+
66+
return False
67+
68+
def get_chain(self, span_id: int) -> List[int]:
69+
"""Get the complete ancestor chain for a span (bottom to top)."""
70+
chain = []
71+
current_id = span_id
72+
visited = set()
73+
74+
while current_id is not None and current_id not in visited:
75+
chain.append(current_id)
76+
visited.add(current_id)
77+
current_id = self._parent_map.get(current_id)
78+
79+
return chain
80+
81+
def clear(self) -> None:
82+
"""Clear all registered spans."""
83+
self._spans.clear()
84+
self._parent_map.clear()
85+
86+
87+
# Global span registry instance
88+
_span_registry = SpanRegistry()
89+
90+
1391
class UiPathTracingManager:
1492
"""Static utility class to manage tracing implementations and decorated functions."""
1593

@@ -61,43 +139,6 @@ def get_parent_context() -> context.Context:
61139
)
62140
return set_span_in_context(bottom_span)
63141

64-
@staticmethod
65-
def _build_ancestor_chain(span: ReadableSpan, spans_by_id: dict) -> List[ReadableSpan]:
66-
"""Build a complete ancestor chain by traversing parents.
67-
68-
Args:
69-
span: The starting span
70-
spans_by_id: Dictionary to populate with discovered spans
71-
72-
Returns:
73-
List of ancestors from bottom (the span) to top (root)
74-
"""
75-
chain = []
76-
current = span
77-
visited = set()
78-
79-
while current:
80-
span_id = current.get_span_context().span_id
81-
82-
# Avoid infinite loops
83-
if span_id in visited:
84-
break
85-
visited.add(span_id)
86-
87-
chain.append(current)
88-
spans_by_id[span_id] = current
89-
90-
# Move to parent if it exists
91-
if current.parent:
92-
parent_id = current.parent.span_id
93-
# Try to get parent from already known spans
94-
current = spans_by_id.get(parent_id)
95-
# If not found, we can't traverse further
96-
else:
97-
break
98-
99-
return chain
100-
101142
@staticmethod
102143
def _get_bottom_most_span(
103144
current_span: ReadableSpan, external_span: ReadableSpan
@@ -114,93 +155,68 @@ def _get_bottom_most_span(
114155
logger.info("=" * 80)
115156
logger.info("Determining bottom-most span...")
116157

117-
current_span_id = current_span.get_span_context().span_id
118-
external_span_id = external_span.get_span_context().span_id
158+
# Register both spans in the registry
159+
_span_registry.register_span(current_span)
160+
_span_registry.register_span(external_span)
119161

120-
# Start by adding external ancestors to our known spans
162+
# Also register external ancestors
121163
external_ancestors = UiPathTracingManager.get_ancestor_spans() or []
122-
spans_by_id = {}
123164
for ancestor in external_ancestors:
124-
spans_by_id[ancestor.get_span_context().span_id] = ancestor
165+
_span_registry.register_span(ancestor)
125166

126-
# Build complete ancestor chains for both spans
127-
logger.info("Building ancestor chain for current_span...")
128-
current_chain = UiPathTracingManager._build_ancestor_chain(current_span, spans_by_id)
129-
130-
logger.info("Building ancestor chain for external_span...")
131-
external_chain = UiPathTracingManager._build_ancestor_chain(external_span, spans_by_id)
167+
current_span_id = current_span.get_span_context().span_id
168+
external_span_id = external_span.get_span_context().span_id
132169

133-
# Build a parent lookup map
134-
parent_map = {} # span_id -> parent_id
135-
for span_id, span in spans_by_id.items():
136-
parent_map[span_id] = span.parent.span_id if span.parent else None
170+
# Get chains from registry
171+
current_chain = _span_registry.get_chain(current_span_id)
172+
external_chain = _span_registry.get_chain(external_span_id)
137173

138-
logger.info(f"current_span chain length (depth): {len(current_chain)}")
139-
logger.info(f"external_span chain length (depth): {len(external_chain)}")
174+
logger.info(f"current_span: '{current_span.name}' depth: {_span_registry.calculate_depth(current_span_id)}")
175+
logger.info(f"external_span: '{external_span.name}' depth: {_span_registry.calculate_depth(external_span_id)}")
140176

141177
# Log the chains
142178
logger.info("current_span chain (bottom to top):")
143-
for i, span in enumerate(current_chain):
144-
span_id = span.get_span_context().span_id
145-
parent_id = f"{span.parent.span_id:016x}" if span.parent else "None"
146-
logger.info(f" [{i}] {span.name} (id: {span_id:016x}, parent: {parent_id})")
179+
for i, span_id in enumerate(current_chain):
180+
span = _span_registry.get_span(span_id)
181+
if span:
182+
parent_id = _span_registry.get_parent_id(span_id)
183+
parent_str = f"{parent_id:016x}" if parent_id is not None else "None"
184+
logger.info(f" [{i}] {span.name} (id: {span_id:016x}, parent: {parent_str})")
147185

148186
logger.info("external_span chain (bottom to top):")
149-
for i, span in enumerate(external_chain):
150-
span_id = span.get_span_context().span_id
151-
parent_id = f"{span.parent.span_id:016x}" if span.parent else "None"
152-
logger.info(f" [{i}] {span.name} (id: {span_id:016x}, parent: {parent_id})")
153-
154-
# Check if one span is an ancestor of the other by walking parent pointers
155-
# Check if external_span is an ancestor of current_span
156-
current_is_descendant_of_external = False
157-
temp_id = current_span_id
158-
depth = 0
159-
while temp_id is not None and depth < 100: # Prevent infinite loops
160-
if temp_id == external_span_id:
161-
current_is_descendant_of_external = True
162-
logger.info(f"current_span is a descendant of external_span (found at depth {depth})")
163-
break
164-
temp_id = parent_map.get(temp_id)
165-
depth += 1
166-
167-
# Check if current_span is an ancestor of external_span
168-
external_is_descendant_of_current = False
169-
temp_id = external_span_id
170-
depth = 0
171-
while temp_id is not None and depth < 100: # Prevent infinite loops
172-
if temp_id == current_span_id:
173-
external_is_descendant_of_current = True
174-
logger.info(f"external_span is a descendant of current_span (found at depth {depth})")
175-
break
176-
temp_id = parent_map.get(temp_id)
177-
depth += 1
178-
179-
# If they're in the same hierarchy (one is ancestor of the other)
180-
if current_is_descendant_of_external:
181-
# current_span is deeper (descendant means further down the tree)
187+
for i, span_id in enumerate(external_chain):
188+
span = _span_registry.get_span(span_id)
189+
if span:
190+
parent_id = _span_registry.get_parent_id(span_id)
191+
parent_str = f"{parent_id:016x}" if parent_id is not None else "None"
192+
logger.info(f" [{i}] {span.name} (id: {span_id:016x}, parent: {parent_str})")
193+
194+
# Check if one span is an ancestor of the other
195+
if _span_registry.is_ancestor(external_span_id, current_span_id):
182196
logger.info("RESULT: current_span is a descendant of external_span -> returning current_span (deeper)")
183197
logger.info("=" * 80)
184198
return current_span
185-
elif external_is_descendant_of_current:
186-
# external_span is deeper (descendant means further down the tree)
199+
elif _span_registry.is_ancestor(current_span_id, external_span_id):
187200
logger.info("RESULT: external_span is a descendant of current_span -> returning external_span (deeper)")
188201
logger.info("=" * 80)
189202
return external_span
190203

191204
# Neither is an ancestor of the other - they're in different branches
192-
# Use chain length as tiebreaker
193-
if len(current_chain) > len(external_chain):
194-
logger.info(f"RESULT: Different branches, current_span is deeper (chain length {len(current_chain)} > {len(external_chain)}) -> returning current_span")
205+
# Use depth as tiebreaker
206+
current_depth = _span_registry.calculate_depth(current_span_id)
207+
external_depth = _span_registry.calculate_depth(external_span_id)
208+
209+
if current_depth > external_depth:
210+
logger.info(f"RESULT: Different branches, current_span is deeper (depth {current_depth} > {external_depth}) -> returning current_span")
195211
logger.info("=" * 80)
196212
return current_span
197-
elif len(external_chain) > len(current_chain):
198-
logger.info(f"RESULT: Different branches, external_span is deeper (chain length {len(external_chain)} > {len(current_chain)}) -> returning external_span")
213+
elif external_depth > current_depth:
214+
logger.info(f"RESULT: Different branches, external_span is deeper (depth {external_depth} > {current_depth}) -> returning external_span")
199215
logger.info("=" * 80)
200216
return external_span
201217
else:
202218
# Same depth, different branches - default to external
203-
logger.info(f"RESULT: Same depth ({len(current_chain)}), different branches -> defaulting to external_span")
219+
logger.info(f"RESULT: Same depth ({current_depth}), different branches -> defaulting to external_span")
204220
logger.info("=" * 80)
205221
return external_span
206222

0 commit comments

Comments
 (0)