Skip to content

Commit 852cdc7

Browse files
authored
feat(profiling): Introduce continuous profiling mode (#2830)
This is a new profiling mode that is mutually exclusive from the existing profiling modes. In the current profiling modes, a profile is always directly attached to a transaction. This new mode will continuously emit chunks of profiling data that will be connected to the span data.
1 parent 1a6a66e commit 852cdc7

19 files changed

+1145
-234
lines changed

docs/apidocs.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ API Docs
3232
.. autoclass:: sentry_sdk.tracing.Span
3333
:members:
3434

35-
.. autoclass:: sentry_sdk.profiler.Profile
35+
.. autoclass:: sentry_sdk.profiler.transaction_profiler.Profile
3636
:members:
3737

3838
.. autoclass:: sentry_sdk.session.Session

sentry_sdk/_types.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,14 @@
153153
"session",
154154
"internal",
155155
"profile",
156+
"profile_chunk",
156157
"metric_bucket",
157158
"monitor",
158159
]
159160
SessionStatus = Literal["ok", "exited", "crashed", "abnormal"]
160161

161-
ProfilerMode = Literal["sleep", "thread", "gevent", "unknown"]
162+
ContinuousProfilerMode = Literal["thread", "gevent", "unknown"]
163+
ProfilerMode = Union[ContinuousProfilerMode, Literal["sleep"]]
162164

163165
# Type of the metric.
164166
MetricType = Literal["d", "s", "g", "c"]

sentry_sdk/client.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@
3333
from sentry_sdk.utils import ContextVar
3434
from sentry_sdk.sessions import SessionFlusher
3535
from sentry_sdk.envelope import Envelope
36-
from sentry_sdk.profiler import has_profiling_enabled, Profile, setup_profiler
36+
from sentry_sdk.profiler.continuous_profiler import setup_continuous_profiler
37+
from sentry_sdk.profiler.transaction_profiler import (
38+
has_profiling_enabled,
39+
Profile,
40+
setup_profiler,
41+
)
3742
from sentry_sdk.scrubber import EventScrubber
3843
from sentry_sdk.monitor import Monitor
3944
from sentry_sdk.spotlight import setup_spotlight
@@ -378,6 +383,14 @@ def _capture_envelope(envelope):
378383
setup_profiler(self.options)
379384
except Exception as e:
380385
logger.debug("Can not set up profiler. (%s)", e)
386+
else:
387+
try:
388+
setup_continuous_profiler(
389+
self.options,
390+
capture_func=_capture_envelope,
391+
)
392+
except Exception as e:
393+
logger.debug("Can not set up continuous profiler. (%s)", e)
381394

382395
finally:
383396
_client_init_debug.set(old_debug)

sentry_sdk/consts.py

+9
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class EndpointType(Enum):
3434

3535
from sentry_sdk._types import (
3636
BreadcrumbProcessor,
37+
ContinuousProfilerMode,
3738
Event,
3839
EventProcessor,
3940
Hint,
@@ -55,6 +56,8 @@ class EndpointType(Enum):
5556
"attach_explain_plans": dict[str, Any],
5657
"max_spans": Optional[int],
5758
"record_sql_params": Optional[bool],
59+
"continuous_profiling_auto_start": Optional[bool],
60+
"continuous_profiling_mode": Optional[ContinuousProfilerMode],
5861
"otel_powered_performance": Optional[bool],
5962
"transport_zlib_compression_level": Optional[int],
6063
"transport_num_pools": Optional[int],
@@ -364,6 +367,12 @@ class SPANDATA:
364367
Example: "MainThread"
365368
"""
366369

370+
PROFILER_ID = "profiler.id"
371+
"""
372+
Label identifying the profiler id that the span occurred in. This should be a string.
373+
Example: "5249fbada8d5416482c2f6e47e337372"
374+
"""
375+
367376

368377
class OP:
369378
ANTHROPIC_MESSAGES_CREATE = "ai.messages.create.anthropic"

sentry_sdk/envelope.py

+10
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ def add_profile(
7373
# type: (...) -> None
7474
self.add_item(Item(payload=PayloadRef(json=profile), type="profile"))
7575

76+
def add_profile_chunk(
77+
self, profile_chunk # type: Any
78+
):
79+
# type: (...) -> None
80+
self.add_item(
81+
Item(payload=PayloadRef(json=profile_chunk), type="profile_chunk")
82+
)
83+
7684
def add_checkin(
7785
self, checkin # type: Any
7886
):
@@ -265,6 +273,8 @@ def data_category(self):
265273
return "internal"
266274
elif ty == "profile":
267275
return "profile"
276+
elif ty == "profile_chunk":
277+
return "profile_chunk"
268278
elif ty == "statsd":
269279
return "metric_bucket"
270280
elif ty == "check_in":

sentry_sdk/profiler/__init__.py

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from sentry_sdk.profiler.continuous_profiler import start_profiler, stop_profiler
2+
from sentry_sdk.profiler.transaction_profiler import (
3+
MAX_PROFILE_DURATION_NS,
4+
PROFILE_MINIMUM_SAMPLES,
5+
Profile,
6+
Scheduler,
7+
ThreadScheduler,
8+
GeventScheduler,
9+
has_profiling_enabled,
10+
setup_profiler,
11+
teardown_profiler,
12+
)
13+
from sentry_sdk.profiler.utils import (
14+
DEFAULT_SAMPLING_FREQUENCY,
15+
MAX_STACK_DEPTH,
16+
get_frame_name,
17+
extract_frame,
18+
extract_stack,
19+
frame_id,
20+
)
21+
22+
__all__ = [
23+
"start_profiler",
24+
"stop_profiler",
25+
# Re-exported for backwards compatibility
26+
"MAX_PROFILE_DURATION_NS",
27+
"PROFILE_MINIMUM_SAMPLES",
28+
"Profile",
29+
"Scheduler",
30+
"ThreadScheduler",
31+
"GeventScheduler",
32+
"has_profiling_enabled",
33+
"setup_profiler",
34+
"teardown_profiler",
35+
"DEFAULT_SAMPLING_FREQUENCY",
36+
"MAX_STACK_DEPTH",
37+
"get_frame_name",
38+
"extract_frame",
39+
"extract_stack",
40+
"frame_id",
41+
]

0 commit comments

Comments
 (0)