1212from opentelemetry .sdk .trace import Span , ReadableSpan , SpanProcessor
1313
1414from sentry_sdk import capture_event
15+ from sentry_sdk .consts import SPANDATA
1516from 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
1623from sentry_sdk .integrations .opentelemetry .utils import (
1724 is_sentry_span ,
1825 convert_from_otel_timestamp ,
1926 extract_span_attributes ,
2027 extract_span_data ,
2128 extract_transaction_name_source ,
2229 get_trace_context ,
30+ get_profile_context ,
2331 get_sentry_meta ,
2432 set_sentry_meta ,
2533)
@@ -54,8 +62,11 @@ def __init__(self):
5462
5563 def on_start (self , span , parent_context = None ):
5664 # 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 )
5970
6071 def on_end (self , span ):
6172 # type: (ReadableSpan) -> None
@@ -94,6 +105,32 @@ def _add_root_span(self, span, parent_span):
94105 # root span points to itself
95106 set_sentry_meta (span , "root_span" , span )
96107
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+
97134 def _flush_root_span (self , span ):
98135 # type: (ReadableSpan) -> None
99136 transaction_event = self ._root_span_to_transaction_event (span )
@@ -147,6 +184,10 @@ def _root_span_to_transaction_event(self, span):
147184 trace_context = get_trace_context (span , span_data = span_data )
148185 contexts = {"trace" : trace_context }
149186
187+ profile_context = get_profile_context (span )
188+ if profile_context :
189+ contexts ["profile" ] = profile_context
190+
150191 if http_status :
151192 contexts ["response" ] = {"status_code" : http_status }
152193
@@ -162,6 +203,13 @@ def _root_span_to_transaction_event(self, span):
162203 }
163204 )
164205
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+
165213 return event
166214
167215 def _span_to_json (self , span ):
0 commit comments