2525from contextlib import contextmanager
2626
2727import sentry_sdk
28- from sentry_sdk ._compat import PY2
28+ from sentry_sdk ._compat import PY33
29+
2930from sentry_sdk ._types import MYPY
31+ from sentry_sdk .utils import nanosecond_time
3032
3133if MYPY :
3234 from typing import Any
4345 FrameData = Tuple [str , str , int ]
4446
4547
46- if PY2 :
47-
48- def nanosecond_time ():
49- # type: () -> int
50- return int (time .clock () * 1e9 )
51-
52- else :
53-
54- def nanosecond_time ():
55- # type: () -> int
56-
57- # In python3.7+, there is a time.perf_counter_ns()
58- # that we may want to switch to for more precision
59- return int (time .perf_counter () * 1e9 )
60-
61-
6248_sample_buffer = None # type: Optional[_SampleBuffer]
6349_scheduler = None # type: Optional[_Scheduler]
6450
@@ -73,6 +59,12 @@ def setup_profiler(options):
7359 buffer_secs = 60
7460 frequency = 101
7561
62+ if not PY33 :
63+ from sentry_sdk .utils import logger
64+
65+ logger .warn ("profiling is only supported on Python >= 3.3" )
66+ return
67+
7668 global _sample_buffer
7769 global _scheduler
7870
@@ -194,19 +186,39 @@ def to_json(self):
194186 assert self ._stop_ns is not None
195187
196188 return {
197- "device_os_name" : platform .system (),
198- "device_os_version" : platform .release (),
199- "duration_ns" : str (self ._stop_ns - self ._start_ns ),
200189 "environment" : None , # Gets added in client.py
190+ "event_id" : uuid .uuid4 ().hex ,
201191 "platform" : "python" ,
202- "platform_version" : platform .python_version (),
203- "profile_id" : uuid .uuid4 ().hex ,
204192 "profile" : _sample_buffer .slice_profile (self ._start_ns , self ._stop_ns ),
205- "trace_id" : self .transaction .trace_id ,
206- "transaction_id" : None , # Gets added in client.py
207- "transaction_name" : self .transaction .name ,
208- "version_code" : "" , # TODO: Determine appropriate value. Currently set to empty string so profile will not get rejected.
209- "version_name" : None , # Gets added in client.py
193+ "release" : None , # Gets added in client.py
194+ "timestamp" : None , # Gets added in client.py
195+ "version" : "1" ,
196+ "device" : {
197+ "architecture" : platform .machine (),
198+ },
199+ "os" : {
200+ "name" : platform .system (),
201+ "version" : platform .release (),
202+ },
203+ "runtime" : {
204+ "name" : platform .python_implementation (),
205+ "version" : platform .python_version (),
206+ },
207+ "transactions" : [
208+ {
209+ "id" : None , # Gets added in client.py
210+ "name" : self .transaction .name ,
211+ # we start the transaction before the profile and this is
212+ # the transaction start time relative to the profile, so we
213+ # hardcode it to 0 until we can start the profile before
214+ "relative_start_ns" : "0" ,
215+ # use the duration of the profile instead of the transaction
216+ # because we end the transaction after the profile
217+ "relative_end_ns" : str (self ._stop_ns - self ._start_ns ),
218+ "trace_id" : self .transaction .trace_id ,
219+ "active_thread_id" : str (self .transaction ._active_thread_id ),
220+ }
221+ ],
210222 }
211223
212224
@@ -245,8 +257,10 @@ def write(self, sample):
245257 self .idx = (idx + 1 ) % self .capacity
246258
247259 def slice_profile (self , start_ns , stop_ns ):
248- # type: (int, int) -> Dict[str, List[ Any] ]
260+ # type: (int, int) -> Dict[str, Any]
249261 samples = [] # type: List[Any]
262+ stacks = dict () # type: Dict[Any, int]
263+ stacks_list = list () # type: List[Any]
250264 frames = dict () # type: Dict[FrameData, int]
251265 frames_list = list () # type: List[Any]
252266
@@ -265,10 +279,10 @@ def slice_profile(self, start_ns, stop_ns):
265279
266280 for tid , stack in raw_sample [1 ]:
267281 sample = {
268- "frames" : [],
269- "relative_timestamp_ns" : ts - start_ns ,
270- "thread_id" : tid ,
282+ "elapsed_since_start_ns" : str (ts - start_ns ),
283+ "thread_id" : str (tid ),
271284 }
285+ current_stack = []
272286
273287 for frame in stack :
274288 if frame not in frames :
@@ -280,11 +294,17 @@ def slice_profile(self, start_ns, stop_ns):
280294 "line" : frame [2 ],
281295 }
282296 )
283- sample ["frames" ].append (frames [frame ])
297+ current_stack .append (frames [frame ])
298+
299+ current_stack = tuple (current_stack )
300+ if current_stack not in stacks :
301+ stacks [current_stack ] = len (stacks )
302+ stacks_list .append (current_stack )
284303
304+ sample ["stack_id" ] = stacks [current_stack ]
285305 samples .append (sample )
286306
287- return {"frames" : frames_list , "samples" : samples }
307+ return {"stacks" : stacks_list , " frames" : frames_list , "samples" : samples }
288308
289309
290310class _Scheduler (object ):
0 commit comments