12
12
from opentelemetry .sdk .trace import Span , ReadableSpan , SpanProcessor
13
13
14
14
from sentry_sdk import capture_event
15
+ from sentry_sdk .consts import SPANDATA
15
16
from sentry_sdk .tracing import DEFAULT_SPAN_ORIGIN
17
+ from sentry_sdk .utils import get_current_thread_meta
18
+ from sentry_sdk .profiler .continuous_profiler import (
19
+ try_autostart_continuous_profiler ,
20
+ get_profiler_id ,
21
+ )
22
+ from sentry_sdk .profiler .transaction_profiler import Profile
16
23
from sentry_sdk .integrations .opentelemetry .utils import (
17
24
is_sentry_span ,
18
25
convert_from_otel_timestamp ,
19
26
extract_span_attributes ,
20
27
extract_span_data ,
21
28
extract_transaction_name_source ,
22
29
get_trace_context ,
30
+ get_profile_context ,
23
31
get_sentry_meta ,
24
32
set_sentry_meta ,
25
33
)
@@ -54,8 +62,11 @@ def __init__(self):
54
62
55
63
def on_start (self , span , parent_context = None ):
56
64
# type: (Span, Optional[Context]) -> None
57
- if not is_sentry_span (span ):
58
- self ._add_root_span (span , get_current_span (parent_context ))
65
+ if is_sentry_span (span ):
66
+ return
67
+
68
+ self ._add_root_span (span , get_current_span (parent_context ))
69
+ self ._start_profile (span )
59
70
60
71
def on_end (self , span ):
61
72
# type: (ReadableSpan) -> None
@@ -94,6 +105,32 @@ def _add_root_span(self, span, parent_span):
94
105
# root span points to itself
95
106
set_sentry_meta (span , "root_span" , span )
96
107
108
+ def _start_profile (self , span ):
109
+ # type: (Span) -> None
110
+ try_autostart_continuous_profiler ()
111
+ profiler_id = get_profiler_id ()
112
+ thread_id , thread_name = get_current_thread_meta ()
113
+
114
+ if profiler_id :
115
+ span .set_attribute (SPANDATA .PROFILER_ID , profiler_id )
116
+ if thread_id :
117
+ span .set_attribute (SPANDATA .THREAD_ID , str (thread_id ))
118
+ if thread_name :
119
+ span .set_attribute (SPANDATA .THREAD_NAME , thread_name )
120
+
121
+ is_root_span = not span .parent or span .parent .is_remote
122
+ sampled = span .context and span .context .trace_flags .sampled
123
+
124
+ if is_root_span and sampled :
125
+ # profiler uses time.perf_counter_ns() so we cannot use the
126
+ # unix timestamp that is on span.start_time
127
+ # setting it to 0 means the profiler will internally measure time on start
128
+ profile = Profile (sampled , 0 )
129
+ # TODO-neel-potel sampling context??
130
+ profile ._set_initial_sampling_decision (sampling_context = {})
131
+ profile .__enter__ ()
132
+ set_sentry_meta (span , "profile" , profile )
133
+
97
134
def _flush_root_span (self , span ):
98
135
# type: (ReadableSpan) -> None
99
136
transaction_event = self ._root_span_to_transaction_event (span )
@@ -147,6 +184,10 @@ def _root_span_to_transaction_event(self, span):
147
184
trace_context = get_trace_context (span , span_data = span_data )
148
185
contexts = {"trace" : trace_context }
149
186
187
+ profile_context = get_profile_context (span )
188
+ if profile_context :
189
+ contexts ["profile" ] = profile_context
190
+
150
191
if http_status :
151
192
contexts ["response" ] = {"status_code" : http_status }
152
193
@@ -162,6 +203,13 @@ def _root_span_to_transaction_event(self, span):
162
203
}
163
204
)
164
205
206
+ profile = cast ("Optional[Profile]" , get_sentry_meta (span , "profile" ))
207
+ if profile :
208
+ profile .__exit__ (None , None , None )
209
+ if profile .valid ():
210
+ event ["profile" ] = profile
211
+ set_sentry_meta (span , "profile" , None )
212
+
165
213
return event
166
214
167
215
def _span_to_json (self , span ):
0 commit comments