Repository: getsentry/sentry-python

Issue:
Port continuous profiler changes to POTEL
See
- https://github.com/getsentry/sentry-python/commit/2724d65aa6739e391bfc19e689b0c7f0f403b4aa
- https://github.com/getsentry/sentry-python/commit/797e82ffb808cb0962c212b39b46204194aabdd9

Discussion:
codecov[bot]: ### :x: 12 Tests Failed:
| Tests completed | Failed | Passed | Skipped |
|---|---|---|---|
| 122 | 12 | 110 | 0 |
<details><summary>View the top 3 failed test(s) by shortest run time</summary>

> 
> ```python
> tests.integrations.httpx.test_httpx test_crumb_capture_client_error[405-warning-httpx_client0]
> ```
> 
> <details><summary>Stack Traces | 0.074s run time</summary>
> 
> > 
> > ```python
> > .../integrations/httpx/test_httpx.py:105: in test_crumb_capture_client_error
> >     assert crumb["level"] == level
> > E   KeyError: 'level'
> > ```
> 
> </details>


> 
> ```python
> tests.integrations.httpx.test_httpx test_crumb_capture_client_error[403-warning-httpx_client0]
> ```
> 
> <details><summary>Stack Traces | 0.075s run time</summary>
> 
> > 
> > ```python
> > .../integrations/httpx/test_httpx.py:105: in test_crumb_capture_client_error
> >     assert crumb["level"] == level
> > E   KeyError: 'level'
> > ```
> 
> </details>


> 
> ```python
> tests.integrations.httpx.test_httpx test_crumb_capture_client_error[500-error-httpx_client1]
> ```
> 
> <details><summary>Stack Traces | 0.075s run time</summary>
> 
> > 
> > ```python
> > .../integrations/httpx/test_httpx.py:105: in test_crumb_capture_client_error
> >     assert crumb["level"] == level
> > E   KeyError: 'level'
> > ```
> 
> </details>

</details>

To view more test analytics, go to the [Test Analytics Dashboard](https://app.codecov.io/gh/getsentry/sentry-python/tests/antonpirker%2Fpotel%2Fcontinuous_profiling)
<sub>📋 Got 3 mins? [Take this short survey](https://forms.gle/BpocVj23nhr2Y45G7) to help us improve Test Analytics.</sub>

Patch:
diff --git a/sentry_sdk/integrations/opentelemetry/span_processor.py b/sentry_sdk/integrations/opentelemetry/span_processor.py
index c7b3fa30..d82d6a03 100644
--- a/sentry_sdk/integrations/opentelemetry/span_processor.py
+++ b/sentry_sdk/integrations/opentelemetry/span_processor.py
@@ -18,6 +18,7 @@ from sentry_sdk.utils import get_current_thread_meta
 from sentry_sdk.profiler.continuous_profiler import (
     try_autostart_continuous_profiler,
     get_profiler_id,
+    try_profile_lifecycle_trace_start,
 )
 from sentry_sdk.profiler.transaction_profiler import Profile
 from sentry_sdk.integrations.opentelemetry.sampler import create_sampling_context
@@ -80,7 +81,8 @@ class SentrySpanProcessor(SpanProcessor):
 
         is_root_span = not span.parent or span.parent.is_remote
         if is_root_span:
-            # if have a root span ending, we build a transaction and send it
+            # if have a root span ending, stop the profiler, build a transaction and send it
+            self._stop_profile(span)
             self._flush_root_span(span)
         else:
             self._append_child_span(span)
@@ -113,6 +115,7 @@ class SentrySpanProcessor(SpanProcessor):
     def _start_profile(self, span):
         # type: (Span) -> None
         try_autostart_continuous_profiler()
+
         profiler_id = get_profiler_id()
         thread_id, thread_name = get_current_thread_meta()
 
@@ -131,6 +134,7 @@ class SentrySpanProcessor(SpanProcessor):
             # unix timestamp that is on span.start_time
             # setting it to 0 means the profiler will internally measure time on start
             profile = Profile(sampled, 0)
+
             sampling_context = create_sampling_context(
                 span.name, span.attributes, span.parent, span.context.trace_id
             )
@@ -138,6 +142,18 @@ class SentrySpanProcessor(SpanProcessor):
             profile.__enter__()
             set_sentry_meta(span, "profile", profile)
 
+            continuous_profile = try_profile_lifecycle_trace_start()
+            profiler_id = get_profiler_id()
+            if profiler_id:
+                span.set_attribute(SPANDATA.PROFILER_ID, profiler_id)
+            set_sentry_meta(span, "continuous_profile", continuous_profile)
+
+    def _stop_profile(self, span):
+        # type: (ReadableSpan) -> None
+        continuous_profiler = get_sentry_meta(span, "continuous_profile")
+        if continuous_profiler:
+            continuous_profiler.stop()
+
     def _flush_root_span(self, span):
         # type: (ReadableSpan) -> None
         transaction_event = self._root_span_to_transaction_event(span)


Test patch:
diff --git a/tests/profiler/test_continuous_profiler.py b/tests/profiler/test_continuous_profiler.py
index 860307e2..a3c3e548 100644
--- a/tests/profiler/test_continuous_profiler.py
+++ b/tests/profiler/test_continuous_profiler.py
@@ -239,7 +239,7 @@ def test_continuous_profiler_auto_start_and_manual_stop(
 
     with sentry_sdk.start_span(name="profiling"):
         with sentry_sdk.start_span(op="op"):
-            time.sleep(0.05)
+            time.sleep(0.1)
 
     assert_single_transaction_with_profile_chunks(envelopes, thread)
 
@@ -250,7 +250,7 @@ def test_continuous_profiler_auto_start_and_manual_stop(
 
         with sentry_sdk.start_span(name="profiling"):
             with sentry_sdk.start_span(op="op"):
-                time.sleep(0.05)
+                time.sleep(0.1)
 
         assert_single_transaction_without_profile_chunks(envelopes)
 
@@ -260,7 +260,7 @@ def test_continuous_profiler_auto_start_and_manual_stop(
 
         with sentry_sdk.start_span(name="profiling"):
             with sentry_sdk.start_span(op="op"):
-                time.sleep(0.05)
+                time.sleep(0.1)
 
         assert_single_transaction_with_profile_chunks(envelopes, thread)