Skip to content

Commit 2c26f42

Browse files
authored
Make profiling work with potel (#3704)
1 parent 8b875c1 commit 2c26f42

File tree

4 files changed

+69
-11
lines changed

4 files changed

+69
-11
lines changed

sentry_sdk/integrations/opentelemetry/potel_span_processor.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,22 @@
1212
from opentelemetry.sdk.trace import Span, ReadableSpan, SpanProcessor
1313

1414
from sentry_sdk import capture_event
15+
from sentry_sdk.consts import SPANDATA
1516
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
1623
from 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):

sentry_sdk/integrations/opentelemetry/sampler.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ def should_sample(
128128
has_traces_sampler = callable(client.options.get("traces_sampler"))
129129
if has_traces_sampler:
130130
# TODO-anton: Make proper sampling_context
131+
# TODO-neel-potel: Make proper sampling_context
131132
sampling_context = {
132133
"transaction_context": {
133134
"name": name,

sentry_sdk/integrations/opentelemetry/utils.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import sentry_sdk
1919
from sentry_sdk.utils import Dsn
20-
from sentry_sdk.consts import SPANSTATUS, OP
20+
from sentry_sdk.consts import SPANSTATUS, OP, SPANDATA
2121
from sentry_sdk.tracing import get_span_status_from_http_code, DEFAULT_SPAN_ORIGIN
2222
from sentry_sdk.tracing_utils import Baggage, LOW_QUALITY_TRANSACTION_SOURCES
2323
from sentry_sdk.integrations.opentelemetry.consts import SentrySpanAttribute
@@ -432,3 +432,15 @@ def set_sentry_meta(span, key, value):
432432
sentry_meta = getattr(span, "_sentry_meta", {})
433433
sentry_meta[key] = value
434434
span._sentry_meta = sentry_meta
435+
436+
437+
def get_profile_context(span):
438+
# type: (ReadableSpan) -> Optional[dict[str, str]]
439+
if not span.attributes:
440+
return None
441+
442+
profiler_id = cast("Optional[str]", span.attributes.get(SPANDATA.PROFILER_ID))
443+
if profiler_id is None:
444+
return None
445+
446+
return {"profiler_id": profiler_id}

sentry_sdk/tracing.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1549,10 +1549,10 @@ def set_thread(self, thread_id, thread_name):
15491549
if thread_name is not None:
15501550
self.set_data(SPANDATA.THREAD_NAME, thread_name)
15511551

1552-
def set_profiler_id(self, profiler_id):
1553-
# type: (Optional[str]) -> None
1554-
if profiler_id is not None:
1555-
self.set_data(SPANDATA.PROFILER_ID, profiler_id)
1552+
def update_active_thread(self):
1553+
# type: () -> None
1554+
thread_id, thread_name = get_current_thread_meta()
1555+
self.set_thread(thread_id, thread_name)
15561556

15571557
def set_http_status(self, http_status):
15581558
# type: (int) -> None
@@ -1576,6 +1576,7 @@ def finish(self, end_timestamp=None):
15761576

15771577
def to_json(self):
15781578
# type: () -> dict[str, Any]
1579+
# TODO-neel-potel for sampling context
15791580
pass
15801581

15811582
def get_trace_context(self):
@@ -1589,10 +1590,6 @@ def get_trace_context(self):
15891590

15901591
return get_trace_context(self._otel_span)
15911592

1592-
def get_profile_context(self):
1593-
# type: () -> Optional[ProfileContext]
1594-
pass
1595-
15961593
def set_context(self, key, value):
15971594
# type: (str, Any) -> None
15981595
from sentry_sdk.integrations.opentelemetry.consts import SentrySpanAttribute

0 commit comments

Comments
 (0)