66import uuid
77from datetime import datetime , timezone
88
9+ from sentry_sdk .consts import VERSION
910from sentry_sdk .envelope import Envelope
1011from sentry_sdk ._lru_cache import LRUCache
1112from sentry_sdk ._types import TYPE_CHECKING
3132 from typing import Type
3233 from typing import Union
3334 from typing_extensions import TypedDict
34- from sentry_sdk ._types import ContinuousProfilerMode
35+ from sentry_sdk ._types import ContinuousProfilerMode , SDKInfo
3536 from sentry_sdk .profiler .utils import (
3637 ExtractedSample ,
3738 FrameId ,
6566_scheduler = None # type: Optional[ContinuousScheduler]
6667
6768
68- def setup_continuous_profiler (options , capture_func ):
69- # type: (Dict[str, Any], Callable[[Envelope], None]) -> bool
69+ def setup_continuous_profiler (options , sdk_info , capture_func ):
70+ # type: (Dict[str, Any], SDKInfo, Callable[[Envelope], None]) -> bool
7071 global _scheduler
7172
7273 if _scheduler is not None :
@@ -91,9 +92,13 @@ def setup_continuous_profiler(options, capture_func):
9192 frequency = DEFAULT_SAMPLING_FREQUENCY
9293
9394 if profiler_mode == ThreadContinuousScheduler .mode :
94- _scheduler = ThreadContinuousScheduler (frequency , options , capture_func )
95+ _scheduler = ThreadContinuousScheduler (
96+ frequency , options , sdk_info , capture_func
97+ )
9598 elif profiler_mode == GeventContinuousScheduler .mode :
96- _scheduler = GeventContinuousScheduler (frequency , options , capture_func )
99+ _scheduler = GeventContinuousScheduler (
100+ frequency , options , sdk_info , capture_func
101+ )
97102 else :
98103 raise ValueError ("Unknown continuous profiler mode: {}" .format (profiler_mode ))
99104
@@ -162,10 +167,11 @@ def get_profiler_id():
162167class ContinuousScheduler (object ):
163168 mode = "unknown" # type: ContinuousProfilerMode
164169
165- def __init__ (self , frequency , options , capture_func ):
166- # type: (int, Dict[str, Any], Callable[[Envelope], None]) -> None
170+ def __init__ (self , frequency , options , sdk_info , capture_func ):
171+ # type: (int, Dict[str, Any], SDKInfo, Callable[[Envelope], None]) -> None
167172 self .interval = 1.0 / frequency
168173 self .options = options
174+ self .sdk_info = sdk_info
169175 self .capture_func = capture_func
170176 self .sampler = self .make_sampler ()
171177 self .buffer = None # type: Optional[ProfileBuffer]
@@ -194,7 +200,7 @@ def pause(self):
194200 def reset_buffer (self ):
195201 # type: () -> None
196202 self .buffer = ProfileBuffer (
197- self .options , PROFILE_BUFFER_SECONDS , self .capture_func
203+ self .options , self . sdk_info , PROFILE_BUFFER_SECONDS , self .capture_func
198204 )
199205
200206 @property
@@ -266,9 +272,9 @@ class ThreadContinuousScheduler(ContinuousScheduler):
266272 mode = "thread" # type: ContinuousProfilerMode
267273 name = "sentry.profiler.ThreadContinuousScheduler"
268274
269- def __init__ (self , frequency , options , capture_func ):
270- # type: (int, Dict[str, Any], Callable[[Envelope], None]) -> None
271- super ().__init__ (frequency , options , capture_func )
275+ def __init__ (self , frequency , options , sdk_info , capture_func ):
276+ # type: (int, Dict[str, Any], SDKInfo, Callable[[Envelope], None]) -> None
277+ super ().__init__ (frequency , options , sdk_info , capture_func )
272278
273279 self .thread = None # type: Optional[threading.Thread]
274280 self .pid = None # type: Optional[int]
@@ -341,13 +347,13 @@ class GeventContinuousScheduler(ContinuousScheduler):
341347
342348 mode = "gevent" # type: ContinuousProfilerMode
343349
344- def __init__ (self , frequency , options , capture_func ):
345- # type: (int, Dict[str, Any], Callable[[Envelope], None]) -> None
350+ def __init__ (self , frequency , options , sdk_info , capture_func ):
351+ # type: (int, Dict[str, Any], SDKInfo, Callable[[Envelope], None]) -> None
346352
347353 if ThreadPool is None :
348354 raise ValueError ("Profiler mode: {} is not available" .format (self .mode ))
349355
350- super ().__init__ (frequency , options , capture_func )
356+ super ().__init__ (frequency , options , sdk_info , capture_func )
351357
352358 self .thread = None # type: Optional[_ThreadPool]
353359 self .pid = None # type: Optional[int]
@@ -405,9 +411,10 @@ def teardown(self):
405411
406412
407413class ProfileBuffer (object ):
408- def __init__ (self , options , buffer_size , capture_func ):
409- # type: (Dict[str, Any], int, Callable[[Envelope], None]) -> None
414+ def __init__ (self , options , sdk_info , buffer_size , capture_func ):
415+ # type: (Dict[str, Any], SDKInfo, int, Callable[[Envelope], None]) -> None
410416 self .options = options
417+ self .sdk_info = sdk_info
411418 self .buffer_size = buffer_size
412419 self .capture_func = capture_func
413420
@@ -445,7 +452,7 @@ def should_flush(self, monotonic_time):
445452
446453 def flush (self ):
447454 # type: () -> None
448- chunk = self .chunk .to_json (self .profiler_id , self .options )
455+ chunk = self .chunk .to_json (self .profiler_id , self .options , self . sdk_info )
449456 envelope = Envelope ()
450457 envelope .add_profile_chunk (chunk )
451458 self .capture_func (envelope )
@@ -491,8 +498,8 @@ def write(self, ts, sample):
491498 # When this happens, we abandon the current sample as it's bad.
492499 capture_internal_exception (sys .exc_info ())
493500
494- def to_json (self , profiler_id , options ):
495- # type: (str, Dict[str, Any]) -> Dict[str, Any]
501+ def to_json (self , profiler_id , options , sdk_info ):
502+ # type: (str, Dict[str, Any], SDKInfo ) -> Dict[str, Any]
496503 profile = {
497504 "frames" : self .frames ,
498505 "stacks" : self .stacks ,
@@ -514,6 +521,10 @@ def to_json(self, profiler_id, options):
514521
515522 payload = {
516523 "chunk_id" : self .chunk_id ,
524+ "client_sdk" : {
525+ "name" : sdk_info ["name" ],
526+ "version" : VERSION ,
527+ },
517528 "platform" : "python" ,
518529 "profile" : profile ,
519530 "profiler_id" : profiler_id ,
0 commit comments