Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(profiling): Extract more frame info #1702

Merged
merged 5 commits into from Nov 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion sentry_sdk/client.py
Expand Up @@ -429,7 +429,7 @@ def capture_event(

if is_transaction:
if profile is not None:
envelope.add_profile(profile.to_json(event_opt))
envelope.add_profile(profile.to_json(event_opt, self.options))
envelope.add_transaction(event_opt)
else:
envelope.add_event(event_opt)
Expand Down
62 changes: 45 additions & 17 deletions sentry_sdk/profiler.py
Expand Up @@ -13,6 +13,7 @@
"""

import atexit
import os
import platform
import random
import signal
Expand All @@ -27,9 +28,15 @@
from sentry_sdk._compat import PY33
from sentry_sdk._queue import Queue
from sentry_sdk._types import MYPY
from sentry_sdk.utils import nanosecond_time
from sentry_sdk.utils import (
filename_for_module,
handle_in_app_impl,
nanosecond_time,
)

RawFrameData = namedtuple("RawFrameData", ["function", "abs_path", "lineno"])
RawFrameData = namedtuple(
"RawFrameData", ["abs_path", "filename", "function", "lineno", "module"]
)

if MYPY:
from types import FrameType
Expand Down Expand Up @@ -61,9 +68,11 @@
ProcessedFrame = TypedDict(
"ProcessedFrame",
{
"abs_path": str,
"filename": Optional[str],
"function": str,
"filename": str,
"lineno": int,
"module": Optional[str],
},
)

Expand Down Expand Up @@ -162,13 +171,24 @@ def extract_stack(frame, max_stack_depth=MAX_STACK_DEPTH):
stack.append(frame)
frame = frame.f_back

return tuple(
RawFrameData(
function=get_frame_name(frame),
abs_path=frame.f_code.co_filename,
lineno=frame.f_lineno,
)
for frame in stack
return tuple(extract_frame(frame) for frame in stack)


def extract_frame(frame):
# type: (FrameType) -> RawFrameData
abs_path = frame.f_code.co_filename

try:
module = frame.f_globals["__name__"]
except Exception:
module = None

return RawFrameData(
abs_path=os.path.abspath(abs_path),
filename=filename_for_module(module, abs_path) or None,
function=get_frame_name(frame),
lineno=frame.f_lineno,
module=module,
)


Expand Down Expand Up @@ -243,18 +263,24 @@ def __exit__(self, ty, value, tb):
self.scheduler.stop_profiling()
self._stop_ns = nanosecond_time()

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

profile = self.scheduler.sample_buffer.slice_profile(
self._start_ns, self._stop_ns
)

handle_in_app_impl(
profile["frames"], options["in_app_exclude"], options["in_app_include"]
)
Comment on lines +275 to +277
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understood the helper correctly, this does the following

  • if no in_app_exclude or in_app_include are specified, it'll add "in_app": True to every frame
  • if it is specified
    • frames that match in_app_include will have "in_app": True added to it
    • frames that match in_app_exclude will have "in_app": False added to it
    • other frames will not specify, but we should treat them as if they're not in_app frames


return {
"environment": event_opt.get("environment"),
"event_id": uuid.uuid4().hex,
"platform": "python",
"profile": self.scheduler.sample_buffer.slice_profile(
self._start_ns, self._stop_ns
),
"profile": profile,
"release": event_opt.get("release", ""),
"timestamp": event_opt["timestamp"],
"version": "1",
Expand Down Expand Up @@ -358,9 +384,11 @@ def slice_profile(self, start_ns, stop_ns):
frames[frame] = len(frames)
frames_list.append(
{
"function": frame.function,
"filename": frame.abs_path,
"abs_path": frame.abs_path,
"function": frame.function or "<unknown>",
"filename": frame.filename,
"lineno": frame.lineno,
"module": frame.module,
}
)

Expand Down