Skip to content

Commit b1290c6

Browse files
authored
feat(profiling): Introduce active thread id on scope (#1764)
Up to this point, simply taking the current thread when the transaction/profile was started was good enough. When using ASGI apps with non async handlers, the request is received on the main thread. This is also where the transaction or profile was started. However, the request is handled on another thread using a thread pool. To support this use case, we want to be able to set the active thread id on the scope where we can read it when we need it to allow the active thread id to be set elsewhere.
1 parent 46697dd commit b1290c6

File tree

3 files changed

+35
-4
lines changed

3 files changed

+35
-4
lines changed

sentry_sdk/client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,9 @@ def capture_event(
433433

434434
if is_transaction:
435435
if profile is not None:
436-
envelope.add_profile(profile.to_json(event_opt, self.options))
436+
envelope.add_profile(
437+
profile.to_json(event_opt, self.options, scope)
438+
)
437439
envelope.add_transaction(event_opt)
438440
else:
439441
envelope.add_event(event_opt)

sentry_sdk/profiler.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
from typing import Sequence
5252
from typing import Tuple
5353
from typing_extensions import TypedDict
54+
import sentry_sdk.scope
5455
import sentry_sdk.tracing
5556

5657
RawStack = Tuple[RawFrameData, ...]
@@ -267,8 +268,8 @@ def __exit__(self, ty, value, tb):
267268
self.scheduler.stop_profiling()
268269
self._stop_ns = nanosecond_time()
269270

270-
def to_json(self, event_opt, options):
271-
# type: (Any, Dict[str, Any]) -> Dict[str, Any]
271+
def to_json(self, event_opt, options, scope):
272+
# type: (Any, Dict[str, Any], Optional[sentry_sdk.scope.Scope]) -> Dict[str, Any]
272273
assert self._start_ns is not None
273274
assert self._stop_ns is not None
274275

@@ -280,6 +281,9 @@ def to_json(self, event_opt, options):
280281
profile["frames"], options["in_app_exclude"], options["in_app_include"]
281282
)
282283

284+
# the active thread id from the scope always take priorty if it exists
285+
active_thread_id = None if scope is None else scope.active_thread_id
286+
283287
return {
284288
"environment": event_opt.get("environment"),
285289
"event_id": uuid.uuid4().hex,
@@ -311,7 +315,11 @@ def to_json(self, event_opt, options):
311315
# because we end the transaction after the profile
312316
"relative_end_ns": str(self._stop_ns - self._start_ns),
313317
"trace_id": self.transaction.trace_id,
314-
"active_thread_id": str(self.transaction._active_thread_id),
318+
"active_thread_id": str(
319+
self.transaction._active_thread_id
320+
if active_thread_id is None
321+
else active_thread_id
322+
),
315323
}
316324
],
317325
}

sentry_sdk/scope.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ class Scope(object):
9494
"_session",
9595
"_attachments",
9696
"_force_auto_session_tracking",
97+
# The thread that is handling the bulk of the work. This can just
98+
# be the main thread, but that's not always true. For web frameworks,
99+
# this would be the thread handling the request.
100+
"_active_thread_id",
97101
)
98102

99103
def __init__(self):
@@ -125,6 +129,8 @@ def clear(self):
125129
self._session = None # type: Optional[Session]
126130
self._force_auto_session_tracking = None # type: Optional[bool]
127131

132+
self._active_thread_id = None # type: Optional[int]
133+
128134
@_attr_setter
129135
def level(self, value):
130136
# type: (Optional[str]) -> None
@@ -228,6 +234,17 @@ def span(self, span):
228234
if transaction.name:
229235
self._transaction = transaction.name
230236

237+
@property
238+
def active_thread_id(self):
239+
# type: () -> Optional[int]
240+
"""Get/set the current active thread id."""
241+
return self._active_thread_id
242+
243+
def set_active_thread_id(self, active_thread_id):
244+
# type: (Optional[int]) -> None
245+
"""Set the current active thread id."""
246+
self._active_thread_id = active_thread_id
247+
231248
def set_tag(
232249
self,
233250
key, # type: str
@@ -447,6 +464,8 @@ def update_from_scope(self, scope):
447464
self._span = scope._span
448465
if scope._attachments:
449466
self._attachments.extend(scope._attachments)
467+
if scope._active_thread_id is not None:
468+
self._active_thread_id = scope._active_thread_id
450469

451470
def update_from_kwargs(
452471
self,
@@ -496,6 +515,8 @@ def __copy__(self):
496515
rv._force_auto_session_tracking = self._force_auto_session_tracking
497516
rv._attachments = list(self._attachments)
498517

518+
rv._active_thread_id = self._active_thread_id
519+
499520
return rv
500521

501522
def __repr__(self):

0 commit comments

Comments
 (0)