32
32
logger = logging .getLogger (__name__ )
33
33
34
34
35
+ # Keys in `sentry_tags` that are shared across all spans in a segment. This list
36
+ # is taken from `extract_shared_tags` in Relay.
37
+ SHARED_TAG_KEYS = (
38
+ "release" ,
39
+ "user" ,
40
+ "user.id" ,
41
+ "user.ip" ,
42
+ "user.username" ,
43
+ "user.email" ,
44
+ "user.geo.country_code" ,
45
+ "user.geo.subregion" ,
46
+ "environment" ,
47
+ "transaction" ,
48
+ "transaction.method" ,
49
+ "transaction.op" ,
50
+ "trace.status" ,
51
+ "mobile" ,
52
+ "os.name" ,
53
+ "device.class" ,
54
+ "browser.name" ,
55
+ "profiler_id" ,
56
+ "sdk.name" ,
57
+ "sdk.version" ,
58
+ "platform" ,
59
+ "thread.id" ,
60
+ "thread.name" ,
61
+ )
62
+
63
+ MOBILE_MAIN_THREAD_NAME = "main"
64
+
65
+
35
66
class Span (SchemaSpan , total = False ):
36
67
start_timestamp_precise : float # Missing in schema
37
68
end_timestamp_precise : float # Missing in schema
@@ -41,15 +72,14 @@ class Span(SchemaSpan, total=False):
41
72
42
73
def process_segment (spans : list [Span ]) -> list [Span ]:
43
74
segment_span = _find_segment_span (spans )
75
+ _enrich_spans (segment_span , spans )
76
+
44
77
if segment_span is None :
45
- # TODO: Handle segments without a defined segment span once all
46
- # functions are refactored to a span interface.
47
78
return spans
48
79
49
80
with metrics .timer ("spans.consumers.process_segments.get_project" ):
50
81
project = Project .objects .get_from_cache (id = segment_span ["project_id" ])
51
82
52
- _enrich_spans (segment_span , spans )
53
83
_create_models (segment_span , project )
54
84
_detect_performance_problems (segment_span , spans , project )
55
85
_record_signals (segment_span , spans , project )
@@ -85,16 +115,73 @@ def _find_segment_span(spans: list[Span]) -> Span | None:
85
115
@metrics .wraps ("spans.consumers.process_segments.enrich_spans" )
86
116
def _enrich_spans (segment : Span | None , spans : list [Span ]) -> None :
87
117
for span in spans :
88
- span ["op" ] = span .get ("sentry_tags" , {}).get ("op" ) or DEFAULT_SPAN_OP
118
+ # TODO: TEST THAT THIS RUNS WITHOUT A SEGMENT SPAN!
119
+ sentry_tags = span .setdefault ("sentry_tags" , {})
120
+ span ["op" ] = sentry_tags .get ("op" ) or DEFAULT_SPAN_OP
121
+ # TODO: port set_span_exclusive_time
89
122
90
- # TODO: Add Relay's enrichment here.
123
+ if segment :
124
+ _set_shared_tags (segment , spans )
91
125
92
126
# Calculate grouping hashes for performance issue detection
93
127
config = load_span_grouping_config ()
94
128
groupings = config .execute_strategy_standalone (spans )
95
129
groupings .write_to_spans (spans )
96
130
97
131
132
+ def _set_shared_tags (segment : Span , spans : list [Span ]) -> None :
133
+ # Assume that Relay has extracted the shared tags into `sentry_tags` on the
134
+ # root span. Once `sentry_tags` is removed, the logic from
135
+ # `extract_shared_tags` should be moved here.
136
+ segment_tags = segment .get ("sentry_tags" , {})
137
+ shared_tags = {k : v for k , v in segment_tags .items () if k in SHARED_TAG_KEYS }
138
+
139
+ is_mobile = segment_tags .get ("mobile" ) == "true"
140
+ mobile_start_type = _get_mobile_start_type (segment )
141
+ ttid_ts = _timestamp_by_op (spans , "ui.load.initial_display" )
142
+ ttfd_ts = _timestamp_by_op (spans , "ui.load.full_display" )
143
+
144
+ for span in spans :
145
+ span_tags = cast (dict [str , Any ], span ["sentry_tags" ])
146
+
147
+ if is_mobile :
148
+ if span_tags .get ("thread.name" ) == MOBILE_MAIN_THREAD_NAME :
149
+ span_tags ["main_thread" ] = "true"
150
+ if not span_tags .get ("app_start_type" ) and mobile_start_type :
151
+ span_tags ["app_start_type" ] = mobile_start_type
152
+
153
+ if ttid_ts is not None and span ["end_timestamp_precise" ] <= ttid_ts :
154
+ span_tags ["ttid" ] = "ttid"
155
+ if ttfd_ts is not None and span ["end_timestamp_precise" ] <= ttfd_ts :
156
+ span_tags ["ttfd" ] = "ttfd"
157
+
158
+ for key , value in shared_tags .items ():
159
+ if span_tags .get (key ) is None :
160
+ span_tags [key ] = value
161
+
162
+
163
+ def _get_mobile_start_type (segment : Span ) -> str | None :
164
+ """
165
+ Check the measurements on the span to determine what kind of start type the
166
+ event is.
167
+ """
168
+ measurements = segment .get ("measurements" ) or {}
169
+
170
+ if "app_start_cold" in measurements :
171
+ return "cold"
172
+ if "app_start_warm" in measurements :
173
+ return "warm"
174
+
175
+ return None
176
+
177
+
178
+ def _timestamp_by_op (spans : list [Span ], op : str ) -> float | None :
179
+ for span in spans :
180
+ if span ["op" ] == op :
181
+ return span ["end_timestamp_precise" ]
182
+ return None
183
+
184
+
98
185
@metrics .wraps ("spans.consumers.process_segments.create_models" )
99
186
def _create_models (segment : Span , project : Project ) -> None :
100
187
"""
0 commit comments