diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index 02741a2f10..bf1e483634 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -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) diff --git a/sentry_sdk/profiler.py b/sentry_sdk/profiler.py index cfe7ff2494..dbb6df53ce 100644 --- a/sentry_sdk/profiler.py +++ b/sentry_sdk/profiler.py @@ -13,6 +13,7 @@ """ import atexit +import os import platform import random import signal @@ -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 @@ -61,9 +68,11 @@ ProcessedFrame = TypedDict( "ProcessedFrame", { + "abs_path": str, + "filename": Optional[str], "function": str, - "filename": str, "lineno": int, + "module": Optional[str], }, ) @@ -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, ) @@ -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"] + ) + 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", @@ -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 "", + "filename": frame.filename, "lineno": frame.lineno, + "module": frame.module, } ) diff --git a/tests/test_profiler.py b/tests/test_profiler.py index d0d3221020..11e92630cf 100644 --- a/tests/test_profiler.py +++ b/tests/test_profiler.py @@ -211,7 +211,22 @@ def _sample_stack(*args, **kwargs): ) def test_thread_scheduler_takes_first_samples(scheduler_class): sample_buffer = DummySampleBuffer( - capacity=1, sample_data=[(0, [(0, (RawFrameData("name", "file", 1),))])] + capacity=1, + sample_data=[ + ( + 0, + [ + ( + 0, + ( + RawFrameData( + "/path/to/file.py", "file.py", "name", 1, "file" + ), + ), + ) + ], + ) + ], ) scheduler = scheduler_class(sample_buffer=sample_buffer, frequency=1000) assert scheduler.start_profiling() @@ -237,7 +252,22 @@ def test_thread_scheduler_takes_first_samples(scheduler_class): def test_thread_scheduler_takes_more_samples(scheduler_class): sample_buffer = DummySampleBuffer( capacity=10, - sample_data=[(i, [(0, (RawFrameData("name", "file", 1),))]) for i in range(3)], + sample_data=[ + ( + i, + [ + ( + 0, + ( + RawFrameData( + "/path/to/file.py", "file.py", "name", 1, "file" + ), + ), + ) + ], + ) + for i in range(3) + ], ) scheduler = scheduler_class(sample_buffer=sample_buffer, frequency=1000) assert scheduler.start_profiling() @@ -330,7 +360,21 @@ def test_thread_scheduler_single_background_thread(scheduler_class): 10, 0, 1, - [(2, [("1", (RawFrameData("name", "file", 1),))])], + [ + ( + 2, + [ + ( + "1", + ( + RawFrameData( + "/path/to/file.py", "file.py", "name", 1, "file" + ), + ), + ) + ], + ) + ], { "frames": [], "samples": [], @@ -343,13 +387,29 @@ def test_thread_scheduler_single_background_thread(scheduler_class): 10, 0, 1, - [(0, [("1", (RawFrameData("name", "file", 1),))])], + [ + ( + 0, + [ + ( + "1", + ( + RawFrameData( + "/path/to/file.py", "file.py", "name", 1, "file" + ), + ), + ) + ], + ) + ], { "frames": [ { + "abs_path": "/path/to/file.py", "function": "name", - "filename": "file", + "filename": "file.py", "lineno": 1, + "module": "file", }, ], "samples": [ @@ -369,15 +429,41 @@ def test_thread_scheduler_single_background_thread(scheduler_class): 0, 1, [ - (0, [("1", (RawFrameData("name", "file", 1),))]), - (1, [("1", (RawFrameData("name", "file", 1),))]), + ( + 0, + [ + ( + "1", + ( + RawFrameData( + "/path/to/file.py", "file.py", "name", 1, "file" + ), + ), + ) + ], + ), + ( + 1, + [ + ( + "1", + ( + RawFrameData( + "/path/to/file.py", "file.py", "name", 1, "file" + ), + ), + ) + ], + ), ], { "frames": [ { + "abs_path": "/path/to/file.py", "function": "name", - "filename": "file", + "filename": "file.py", "lineno": 1, + "module": "file", }, ], "samples": [ @@ -402,15 +488,31 @@ def test_thread_scheduler_single_background_thread(scheduler_class): 0, 1, [ - (0, [("1", (RawFrameData("name1", "file", 1),))]), + ( + 0, + [ + ( + "1", + ( + RawFrameData( + "/path/to/file.py", "file.py", "name1", 1, "file" + ), + ), + ) + ], + ), ( 1, [ ( "1", ( - RawFrameData("name1", "file", 1), - RawFrameData("name2", "file", 2), + RawFrameData( + "/path/to/file.py", "file.py", "name1", 1, "file" + ), + RawFrameData( + "/path/to/file.py", "file.py", "name2", 2, "file" + ), ), ) ], @@ -419,14 +521,18 @@ def test_thread_scheduler_single_background_thread(scheduler_class): { "frames": [ { + "abs_path": "/path/to/file.py", "function": "name1", - "filename": "file", + "filename": "file.py", "lineno": 1, + "module": "file", }, { + "abs_path": "/path/to/file.py", "function": "name2", - "filename": "file", + "filename": "file.py", "lineno": 2, + "module": "file", }, ], "samples": [ @@ -457,8 +563,12 @@ def test_thread_scheduler_single_background_thread(scheduler_class): ( "1", ( - RawFrameData("name1", "file", 1), - RawFrameData("name2", "file", 2), + RawFrameData( + "/path/to/file.py", "file.py", "name1", 1, "file" + ), + RawFrameData( + "/path/to/file.py", "file.py", "name2", 2, "file" + ), ), ) ], @@ -469,8 +579,12 @@ def test_thread_scheduler_single_background_thread(scheduler_class): ( "1", ( - RawFrameData("name3", "file", 3), - RawFrameData("name4", "file", 4), + RawFrameData( + "/path/to/file.py", "file.py", "name3", 3, "file" + ), + RawFrameData( + "/path/to/file.py", "file.py", "name4", 4, "file" + ), ), ) ], @@ -479,24 +593,32 @@ def test_thread_scheduler_single_background_thread(scheduler_class): { "frames": [ { + "abs_path": "/path/to/file.py", "function": "name1", - "filename": "file", + "filename": "file.py", "lineno": 1, + "module": "file", }, { + "abs_path": "/path/to/file.py", "function": "name2", - "filename": "file", + "filename": "file.py", "lineno": 2, + "module": "file", }, { + "abs_path": "/path/to/file.py", "function": "name3", - "filename": "file", + "filename": "file.py", "lineno": 3, + "module": "file", }, { + "abs_path": "/path/to/file.py", "function": "name4", - "filename": "file", + "filename": "file.py", "lineno": 4, + "module": "file", }, ], "samples": [ @@ -521,15 +643,31 @@ def test_thread_scheduler_single_background_thread(scheduler_class): 0, 1, [ - (0, [("1", (RawFrameData("name1", "file", 1),))]), + ( + 0, + [ + ( + "1", + ( + RawFrameData( + "/path/to/file.py", "file.py", "name1", 1, "file" + ), + ), + ) + ], + ), ( 1, [ ( "1", ( - RawFrameData("name2", "file", 2), - RawFrameData("name3", "file", 3), + RawFrameData( + "/path/to/file.py", "file.py", "name2", 2, "file" + ), + RawFrameData( + "/path/to/file.py", "file.py", "name3", 3, "file" + ), ), ) ], @@ -538,14 +676,18 @@ def test_thread_scheduler_single_background_thread(scheduler_class): { "frames": [ { + "abs_path": "/path/to/file.py", "function": "name2", - "filename": "file", + "filename": "file.py", "lineno": 2, + "module": "file", }, { + "abs_path": "/path/to/file.py", "function": "name3", - "filename": "file", + "filename": "file.py", "lineno": 3, + "module": "file", }, ], "samples": [