@@ -79,71 +79,91 @@ def span_event(
7979 if not partial_granularity_sampled :
8080 # intrinsics, user attrs, agent attrs
8181 return [i_attrs , u_attrs , a_attrs ]
82- else :
83- if ct_exit_spans is None :
84- ct_exit_spans = {}
85-
86- partial_granularity_type = settings .distributed_tracing .sampler .partial_granularity .type
87- exit_span_attrs_present = attribute .SPAN_ENTITY_RELATIONSHIP_ATTRIBUTES & set (a_attrs )
88- # If this is the entry node or an LLM span always return it.
89- if i_attrs .get ("nr.entryPoint" ) or i_attrs ["name" ].startswith ("Llm/" ):
90- if partial_granularity_type == "reduced" :
91- return [i_attrs , u_attrs , a_attrs ]
92- else :
93- return [i_attrs , {}, {}]
94- # If the span is not an exit span, skip it by returning None.
95- if not exit_span_attrs_present :
96- return None
97- # If the span is an exit span and we are in reduced mode (meaning no attribute dropping),
98- # just return the exit span as is.
82+
83+ if ct_exit_spans is None :
84+ ct_exit_spans = {"instrumented" : 0 , "kept" : 0 , "dropped_ids" : 0 }
85+ ct_exit_spans ["instrumented" ] += 1
86+
87+ partial_granularity_type = settings .distributed_tracing .sampler .partial_granularity .type
88+ a_attrs_set = set (a_attrs )
89+ exit_span_attrs_present = attribute .SPAN_ENTITY_RELATIONSHIP_ATTRIBUTES & a_attrs_set
90+ exit_span_error_attrs_present = attribute .SPAN_ERROR_ATTRIBUTES & a_attrs_set
91+ # If this is an entry span, add `nr.pg` to indicate transaction is partial
92+ # granularity sampled.
93+ if i_attrs .get ("nr.entryPoint" ):
94+ a_attrs ["nr.pg" ] = True
95+ # If this is the entry node or an LLM span always return it.
96+ if i_attrs .get ("nr.entryPoint" ) or i_attrs ["name" ].startswith ("Llm/" ):
97+ ct_exit_spans ["kept" ] += 1
9998 if partial_granularity_type == "reduced" :
10099 return [i_attrs , u_attrs , a_attrs ]
101100 else :
102- a_minimized_attrs = attr_class ({key : a_attrs [key ] for key in exit_span_attrs_present })
103- # If we are in essential mode return the span with minimized attributes.
104- if partial_granularity_type == "essential" :
105- return [i_attrs , {}, a_minimized_attrs ]
106- # If the span is an exit span but span compression (compact) is enabled,
107- # we need to check for uniqueness before returning it.
108- # Combine all the entity relationship attr values into a string to be
109- # used as the hash to check for uniqueness.
110- span_attrs = "" .join ([str (a_minimized_attrs [key ]) for key in exit_span_attrs_present ])
111- new_exit_span = span_attrs not in ct_exit_spans
112- # If this is a new exit span, add it to the known ct_exit_spans and
113- # return it.
114- if new_exit_span :
115- # nr.ids is the list of span guids that share this unqiue exit span.
116- a_minimized_attrs ["nr.ids" ] = []
117- a_minimized_attrs ["nr.durations" ] = self .duration
118- ct_exit_spans [span_attrs ] = [i_attrs , a_minimized_attrs ]
119- return [i_attrs , {}, a_minimized_attrs ]
120- # If this is an exit span we've already seen, add it's guid to the list
121- # of ids on the seen span, compute the new duration & start time, and
122- # return None.
101+ return [i_attrs , {}, {key : a_attrs .get (key ) for key in exit_span_error_attrs_present | {"nr.pg" }}]
102+ # If the span is not an exit span, skip it by returning None.
103+ if not exit_span_attrs_present :
104+ return None
105+ # If the span is an exit span and we are in reduced mode (meaning no attribute dropping),
106+ # just return the exit span as is.
107+ if partial_granularity_type == "reduced" :
108+ ct_exit_spans ["kept" ] += 1
109+ return [i_attrs , u_attrs , a_attrs ]
110+ else :
111+ a_minimized_attrs = attr_class (
112+ {key : a_attrs [key ] for key in (exit_span_attrs_present | exit_span_error_attrs_present )}
113+ )
114+ # If we are in essential mode return the span with minimized attributes.
115+ if partial_granularity_type == "essential" :
116+ ct_exit_spans ["kept" ] += 1
117+ return [i_attrs , {}, a_minimized_attrs ]
118+ # If the span is an exit span but span compression (compact) is enabled,
119+ # we need to check for uniqueness before returning it.
120+ # Combine all the entity relationship attr values into a string to be
121+ # used as the hash to check for uniqueness.
122+ span_attrs = "" .join ([str (a_minimized_attrs [key ]) for key in exit_span_attrs_present ])
123+ new_exit_span = span_attrs not in ct_exit_spans
124+ # If this is a new exit span, add it to the known ct_exit_spans and
125+ # return it.
126+ if new_exit_span :
127+ # nr.ids is the list of span guids that share this unqiue exit span.
128+ a_minimized_attrs ["nr.ids" ] = []
129+ a_minimized_attrs ["nr.durations" ] = self .duration
130+ ct_exit_spans [span_attrs ] = [i_attrs , a_minimized_attrs ]
131+ ct_exit_spans ["kept" ] += 1
132+ return [i_attrs , {}, a_minimized_attrs ]
133+ # If this is an exit span we've already seen, add the error attributes
134+ # (last occurring error takes precedence), add it's guid to the list
135+ # of ids on the seen span, compute the new duration & start time, and
136+ # return None.
137+ ct_exit_spans [span_attrs ][1 ].update (
138+ attr_class ({key : a_minimized_attrs [key ] for key in exit_span_error_attrs_present })
139+ )
140+ # Max size for `nr.ids` = 1024. Max length = 63 (each span id is 16 bytes + 8 bytes for list type).
141+ if len (ct_exit_spans [span_attrs ][1 ]["nr.ids" ]) < 63 :
123142 ct_exit_spans [span_attrs ][1 ]["nr.ids" ].append (self .guid )
124- # Max size for `nr.ids` = 1024. Max length = 63 (each span id is 16 bytes + 8 bytes for list type).
125- ct_exit_spans [span_attrs ][1 ]["nr.ids" ] = ct_exit_spans [span_attrs ][1 ]["nr.ids" ][:63 ]
126- # Compute the new start and end time for all compressed spans and use
127- # that to set the duration for all compressed spans.
128- current_start_time = ct_exit_spans [span_attrs ][0 ]["timestamp" ]
129- current_end_time = (
130- ct_exit_spans [span_attrs ][0 ]["timestamp" ] / 1000 + ct_exit_spans [span_attrs ][1 ]["nr.durations" ]
131- )
132- new_start_time = i_attrs ["timestamp" ]
133- new_end_time = i_attrs ["timestamp" ] / 1000 + i_attrs ["duration" ]
134- set_start_time = min (new_start_time , current_start_time )
135- # If the new span starts after the old span's end time or the new span
136- # ends before the current span starts; add the durations.
137- if current_end_time < new_start_time / 1000 or new_end_time < current_start_time / 1000 :
138- set_duration = ct_exit_spans [span_attrs ][1 ]["nr.durations" ] + i_attrs ["duration" ]
139- # Otherwise, if the new and old span's overlap in time, use the newest
140- # end time and subtract the start time from it to calculate the new
141- # duration.
142- else :
143- set_duration = max (current_end_time , new_end_time ) - set_start_time / 1000
144- ct_exit_spans [span_attrs ][0 ]["timestamp" ] = set_start_time
145- ct_exit_spans [span_attrs ][1 ]["nr.durations" ] = set_duration
146- return None
143+ else :
144+ ct_exit_spans ["dropped_ids" ] += 1
145+
146+ ct_exit_spans [span_attrs ][1 ]["nr.ids" ] = ct_exit_spans [span_attrs ][1 ]["nr.ids" ][:63 ]
147+ # Compute the new start and end time for all compressed spans and use
148+ # that to set the duration for all compressed spans.
149+ current_start_time = ct_exit_spans [span_attrs ][0 ]["timestamp" ]
150+ current_end_time = (
151+ ct_exit_spans [span_attrs ][0 ]["timestamp" ] / 1000 + ct_exit_spans [span_attrs ][1 ]["nr.durations" ]
152+ )
153+ new_start_time = i_attrs ["timestamp" ]
154+ new_end_time = i_attrs ["timestamp" ] / 1000 + i_attrs ["duration" ]
155+ set_start_time = min (new_start_time , current_start_time )
156+ # If the new span starts after the old span's end time or the new span
157+ # ends before the current span starts; add the durations.
158+ if current_end_time < new_start_time / 1000 or new_end_time < current_start_time / 1000 :
159+ set_duration = ct_exit_spans [span_attrs ][1 ]["nr.durations" ] + i_attrs ["duration" ]
160+ # Otherwise, if the new and old span's overlap in time, use the newest
161+ # end time and subtract the start time from it to calculate the new
162+ # duration.
163+ else :
164+ set_duration = max (current_end_time , new_end_time ) - set_start_time / 1000
165+ ct_exit_spans [span_attrs ][0 ]["timestamp" ] = set_start_time
166+ ct_exit_spans [span_attrs ][1 ]["nr.durations" ] = set_duration
147167
148168 def span_events (
149169 self ,
@@ -162,13 +182,11 @@ def span_events(
162182 partial_granularity_sampled = partial_granularity_sampled ,
163183 ct_exit_spans = ct_exit_spans ,
164184 )
165- ct_exit_spans ["instrumented" ] += 1
166185 parent_id = parent_guid
167186 if span : # span will be None if the span is an inprocess span or repeated exit span.
168- ct_exit_spans ["kept" ] += 1
169187 yield span
170188 # Compressed spans are always reparented onto the entry span.
171- if not settings .distributed_tracing .sampler .partial_granularity .type = = "compact" or span [0 ].get (
189+ if settings .distributed_tracing .sampler .partial_granularity .type ! = "compact" or span [0 ].get (
172190 "nr.entryPoint"
173191 ):
174192 parent_id = self .guid
@@ -181,9 +199,7 @@ def span_events(
181199 partial_granularity_sampled = partial_granularity_sampled ,
182200 ct_exit_spans = ct_exit_spans ,
183201 ):
184- ct_exit_spans ["instrumented" ] += 1
185202 if event : # event will be None if the span is an inprocess span or repeated exit span.
186- ct_exit_spans ["kept" ] += 1
187203 yield event
188204
189205
0 commit comments