2929from sentry_sdk ._types import MYPY
3030from sentry_sdk .utils import nanosecond_time
3131
32+ RawFrameData = namedtuple ("RawFrameData" , ["function" , "abs_path" , "lineno" ])
33+
3234if MYPY :
3335 from types import FrameType
3436 from typing import Any
3941 from typing import List
4042 from typing import Optional
4143 from typing import Sequence
44+ from typing import Tuple
45+ from typing_extensions import TypedDict
4246 import sentry_sdk .tracing
4347
44-
45- FrameData = namedtuple ("FrameData" , ["name" , "file" , "line" ])
48+ RawSampleData = Tuple [int , Sequence [Tuple [int , Sequence [RawFrameData ]]]]
49+
50+ ProcessedStack = Tuple [int , ...]
51+
52+ ProcessedSample = TypedDict (
53+ "ProcessedSample" ,
54+ {
55+ "elapsed_since_start_ns" : str ,
56+ "thread_id" : str ,
57+ "stack_id" : int ,
58+ },
59+ )
60+
61+ ProcessedFrame = TypedDict (
62+ "ProcessedFrame" ,
63+ {
64+ "function" : str ,
65+ "filename" : str ,
66+ "lineno" : int ,
67+ },
68+ )
69+
70+ ProcessedThreadMetadata = TypedDict (
71+ "ProcessedThreadMetadata" ,
72+ {"name" : str },
73+ )
74+
75+ ProcessedProfile = TypedDict (
76+ "ProcessedProfile" ,
77+ {
78+ "frames" : List [ProcessedFrame ],
79+ "stacks" : List [ProcessedStack ],
80+ "samples" : List [ProcessedSample ],
81+ "thread_metadata" : Dict [str , ProcessedThreadMetadata ],
82+ },
83+ )
4684
4785
4886_sample_buffer = None # type: Optional[SampleBuffer]
@@ -132,7 +170,7 @@ def _sample_stack(*args, **kwargs):
132170
133171
134172def extract_stack (frame , max_stack_depth = MAX_STACK_DEPTH ):
135- # type: (Optional[FrameType], int) -> Sequence[FrameData ]
173+ # type: (Optional[FrameType], int) -> Sequence[RawFrameData ]
136174 """
137175 Extracts the stack starting the specified frame. The extracted stack
138176 assumes the specified frame is the top of the stack, and works back
@@ -149,10 +187,10 @@ def extract_stack(frame, max_stack_depth=MAX_STACK_DEPTH):
149187 frame = frame .f_back
150188
151189 return [
152- FrameData (
153- name = get_frame_name (frame ),
154- file = frame .f_code .co_filename ,
155- line = frame .f_lineno ,
190+ RawFrameData (
191+ function = get_frame_name (frame ),
192+ abs_path = frame .f_code .co_filename ,
193+ lineno = frame .f_lineno ,
156194 )
157195 for frame in stack
158196 ]
@@ -268,12 +306,12 @@ class SampleBuffer(object):
268306 def __init__ (self , capacity ):
269307 # type: (int) -> None
270308
271- self .buffer = [None ] * capacity
272- self .capacity = capacity
273- self .idx = 0
309+ self .buffer = [None ] * capacity # type: List[Optional[RawSampleData]]
310+ self .capacity = capacity # type: int
311+ self .idx = 0 # type: int
274312
275313 def write (self , sample ):
276- # type: (Any ) -> None
314+ # type: (RawSampleData ) -> None
277315 """
278316 Writing to the buffer is not thread safe. There is the possibility
279317 that parallel writes will overwrite one another.
@@ -290,12 +328,12 @@ def write(self, sample):
290328 self .idx = (idx + 1 ) % self .capacity
291329
292330 def slice_profile (self , start_ns , stop_ns ):
293- # type: (int, int) -> Dict[str, Any]
294- samples = [] # type: List[Any ]
295- stacks = dict () # type: Dict[Any , int]
296- stacks_list = list () # type: List[Any ]
297- frames = dict () # type: Dict[FrameData , int]
298- frames_list = list () # type: List[Any ]
331+ # type: (int, int) -> ProcessedProfile
332+ samples = [] # type: List[ProcessedSample ]
333+ stacks = dict () # type: Dict[ProcessedStack , int]
334+ stacks_list = list () # type: List[ProcessedStack ]
335+ frames = dict () # type: Dict[RawFrameData , int]
336+ frames_list = list () # type: List[ProcessedFrame ]
299337
300338 # TODO: This is doing an naive iteration over the
301339 # buffer and extracting the appropriate samples.
@@ -311,20 +349,16 @@ def slice_profile(self, start_ns, stop_ns):
311349 continue
312350
313351 for tid , stack in raw_sample [1 ]:
314- sample = {
315- "elapsed_since_start_ns" : str (ts - start_ns ),
316- "thread_id" : str (tid ),
317- }
318352 current_stack = []
319353
320354 for frame in stack :
321355 if frame not in frames :
322356 frames [frame ] = len (frames )
323357 frames_list .append (
324358 {
325- "name " : frame .name ,
326- "file " : frame .file ,
327- "line " : frame .line ,
359+ "function " : frame .function ,
360+ "filename " : frame .abs_path ,
361+ "lineno " : frame .lineno ,
328362 }
329363 )
330364 current_stack .append (frames [frame ])
@@ -334,8 +368,13 @@ def slice_profile(self, start_ns, stop_ns):
334368 stacks [current_stack ] = len (stacks )
335369 stacks_list .append (current_stack )
336370
337- sample ["stack_id" ] = stacks [current_stack ]
338- samples .append (sample )
371+ samples .append (
372+ {
373+ "elapsed_since_start_ns" : str (ts - start_ns ),
374+ "thread_id" : str (tid ),
375+ "stack_id" : stacks [current_stack ],
376+ }
377+ )
339378
340379 # This collects the thread metadata at the end of a profile. Doing it
341380 # this way means that any threads that terminate before the profile ends
@@ -345,7 +384,7 @@ def slice_profile(self, start_ns, stop_ns):
345384 "name" : thread .name ,
346385 }
347386 for thread in threading .enumerate ()
348- }
387+ } # type: Dict[str, ProcessedThreadMetadata]
349388
350389 return {
351390 "stacks" : stacks_list ,
0 commit comments