Skip to content

Commit

Permalink
feat(profiling): Introduce active thread id on scope (#1764)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Zylphrex authored Dec 7, 2022
1 parent 46697dd commit b1290c6
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 4 deletions.
4 changes: 3 additions & 1 deletion sentry_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,9 @@ def capture_event(

if is_transaction:
if profile is not None:
envelope.add_profile(profile.to_json(event_opt, self.options))
envelope.add_profile(
profile.to_json(event_opt, self.options, scope)
)
envelope.add_transaction(event_opt)
else:
envelope.add_event(event_opt)
Expand Down
14 changes: 11 additions & 3 deletions sentry_sdk/profiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
from typing import Sequence
from typing import Tuple
from typing_extensions import TypedDict
import sentry_sdk.scope
import sentry_sdk.tracing

RawStack = Tuple[RawFrameData, ...]
Expand Down Expand Up @@ -267,8 +268,8 @@ def __exit__(self, ty, value, tb):
self.scheduler.stop_profiling()
self._stop_ns = nanosecond_time()

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

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

# the active thread id from the scope always take priorty if it exists
active_thread_id = None if scope is None else scope.active_thread_id

return {
"environment": event_opt.get("environment"),
"event_id": uuid.uuid4().hex,
Expand Down Expand Up @@ -311,7 +315,11 @@ def to_json(self, event_opt, options):
# because we end the transaction after the profile
"relative_end_ns": str(self._stop_ns - self._start_ns),
"trace_id": self.transaction.trace_id,
"active_thread_id": str(self.transaction._active_thread_id),
"active_thread_id": str(
self.transaction._active_thread_id
if active_thread_id is None
else active_thread_id
),
}
],
}
Expand Down
21 changes: 21 additions & 0 deletions sentry_sdk/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ class Scope(object):
"_session",
"_attachments",
"_force_auto_session_tracking",
# The thread that is handling the bulk of the work. This can just
# be the main thread, but that's not always true. For web frameworks,
# this would be the thread handling the request.
"_active_thread_id",
)

def __init__(self):
Expand Down Expand Up @@ -125,6 +129,8 @@ def clear(self):
self._session = None # type: Optional[Session]
self._force_auto_session_tracking = None # type: Optional[bool]

self._active_thread_id = None # type: Optional[int]

@_attr_setter
def level(self, value):
# type: (Optional[str]) -> None
Expand Down Expand Up @@ -228,6 +234,17 @@ def span(self, span):
if transaction.name:
self._transaction = transaction.name

@property
def active_thread_id(self):
# type: () -> Optional[int]
"""Get/set the current active thread id."""
return self._active_thread_id

def set_active_thread_id(self, active_thread_id):
# type: (Optional[int]) -> None
"""Set the current active thread id."""
self._active_thread_id = active_thread_id

def set_tag(
self,
key, # type: str
Expand Down Expand Up @@ -447,6 +464,8 @@ def update_from_scope(self, scope):
self._span = scope._span
if scope._attachments:
self._attachments.extend(scope._attachments)
if scope._active_thread_id is not None:
self._active_thread_id = scope._active_thread_id

def update_from_kwargs(
self,
Expand Down Expand Up @@ -496,6 +515,8 @@ def __copy__(self):
rv._force_auto_session_tracking = self._force_auto_session_tracking
rv._attachments = list(self._attachments)

rv._active_thread_id = self._active_thread_id

return rv

def __repr__(self):
Expand Down

0 comments on commit b1290c6

Please sign in to comment.