diff --git a/sentry_sdk/profiler.py b/sentry_sdk/profiler.py index 884fb70af5..94080aed89 100644 --- a/sentry_sdk/profiler.py +++ b/sentry_sdk/profiler.py @@ -103,6 +103,11 @@ }, ) + ProfileContext = TypedDict( + "ProfileContext", + {"profile_id": str}, + ) + try: from gevent.monkey import is_module_patched # type: ignore except ImportError: @@ -343,6 +348,7 @@ def __init__( self.start_ns = 0 # type: int self.stop_ns = 0 # type: int self.active = False # type: bool + self.event_id = uuid.uuid4().hex # type: str self.indexed_frames = {} # type: Dict[RawFrame, int] self.indexed_stacks = {} # type: Dict[RawStackId, int] @@ -352,6 +358,10 @@ def __init__( transaction._profile = self + def get_profile_context(self): + # type: () -> ProfileContext + return {"profile_id": self.event_id} + def __enter__(self): # type: () -> None hub = self.hub or sentry_sdk.Hub.current @@ -444,7 +454,7 @@ def to_json(self, event_opt, options): return { "environment": event_opt.get("environment"), - "event_id": uuid.uuid4().hex, + "event_id": self.event_id, "platform": "python", "profile": profile, "release": event_opt.get("release", ""), diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index b72524f734..61c6a7190b 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -709,6 +709,7 @@ def finish(self, hub=None, end_timestamp=None): if hub.client is not None and self._profile is not None: event["profile"] = self._profile + contexts.update({"profile": self._profile.get_profile_context()}) if has_custom_measurements_enabled(): event["measurements"] = self._measurements diff --git a/tests/integrations/wsgi/test_wsgi.py b/tests/integrations/wsgi/test_wsgi.py index 3ca9c5e9e7..dae9b26c13 100644 --- a/tests/integrations/wsgi/test_wsgi.py +++ b/tests/integrations/wsgi/test_wsgi.py @@ -323,3 +323,36 @@ def test_app(environ, start_response): for item in envelope.items: count_item_types[item.type] += 1 assert count_item_types["profile"] == profile_count + + +def test_profile_context_sent(sentry_init, capture_envelopes, teardown_profiling): + def test_app(environ, start_response): + start_response("200 OK", []) + return ["Go get the ball! Good dog!"] + + sentry_init( + traces_sample_rate=1.0, + _experiments={"profiles_sample_rate": 1.0}, + ) + app = SentryWsgiMiddleware(test_app) + envelopes = capture_envelopes() + + client = Client(app) + client.get("/") + + transaction = None + profile = None + for envelope in envelopes: + for item in envelope.items: + if item.type == "profile": + assert profile is None # should only have 1 profile + profile = item + elif item.type == "transaction": + assert transaction is None # should only have 1 transaction + transaction = item + + assert transaction is not None + assert profile is not None + assert transaction.payload.json["contexts"]["profile"] == { + "profile_id": profile.payload.json["event_id"], + }