@@ -111,17 +111,16 @@ def setup_profiler(options):
111111 # To buffer samples for `buffer_secs` at `frequency` Hz, we need
112112 # a capcity of `buffer_secs * frequency`.
113113 _sample_buffer = SampleBuffer (capacity = buffer_secs * frequency )
114- _sampler = _init_sample_stack_fn (_sample_buffer )
115114
116115 profiler_mode = options ["_experiments" ].get ("profiler_mode" , SigprofScheduler .mode )
117116 if profiler_mode == SigprofScheduler .mode :
118- _scheduler = SigprofScheduler (sampler = _sampler , frequency = frequency )
117+ _scheduler = SigprofScheduler (sample_buffer = _sample_buffer , frequency = frequency )
119118 elif profiler_mode == SigalrmScheduler .mode :
120- _scheduler = SigalrmScheduler (sampler = _sampler , frequency = frequency )
119+ _scheduler = SigalrmScheduler (sample_buffer = _sample_buffer , frequency = frequency )
121120 elif profiler_mode == SleepScheduler .mode :
122- _scheduler = SleepScheduler (sampler = _sampler , frequency = frequency )
121+ _scheduler = SleepScheduler (sample_buffer = _sample_buffer , frequency = frequency )
123122 elif profiler_mode == EventScheduler .mode :
124- _scheduler = EventScheduler (sampler = _sampler , frequency = frequency )
123+ _scheduler = EventScheduler (sample_buffer = _sample_buffer , frequency = frequency )
125124 else :
126125 raise ValueError ("Unknown profiler mode: {}" .format (profiler_mode ))
127126 _scheduler .setup ()
@@ -142,29 +141,6 @@ def teardown_profiler():
142141 _scheduler = None
143142
144143
145- def _init_sample_stack_fn (buffer ):
146- # type: (SampleBuffer) -> Callable[..., None]
147-
148- def _sample_stack (* args , ** kwargs ):
149- # type: (*Any, **Any) -> None
150- """
151- Take a sample of the stack on all the threads in the process.
152- This should be called at a regular interval to collect samples.
153- """
154-
155- buffer .write (
156- (
157- nanosecond_time (),
158- [
159- (tid , extract_stack (frame ))
160- for tid , frame in sys ._current_frames ().items ()
161- ],
162- )
163- )
164-
165- return _sample_stack
166-
167-
168144# We want to impose a stack depth limit so that samples aren't too large.
169145MAX_STACK_DEPTH = 128
170146
@@ -242,8 +218,14 @@ def get_frame_name(frame):
242218
243219
244220class Profile (object ):
245- def __init__ (self , transaction , hub = None ):
246- # type: (sentry_sdk.tracing.Transaction, Optional[sentry_sdk.Hub]) -> None
221+ def __init__ (
222+ self ,
223+ scheduler , # type: Scheduler
224+ transaction , # type: sentry_sdk.tracing.Transaction
225+ hub = None , # type: Optional[sentry_sdk.Hub]
226+ ):
227+ # type: (...) -> None
228+ self .scheduler = scheduler
247229 self .transaction = transaction
248230 self .hub = hub
249231 self ._start_ns = None # type: Optional[int]
@@ -253,27 +235,26 @@ def __init__(self, transaction, hub=None):
253235
254236 def __enter__ (self ):
255237 # type: () -> None
256- assert _scheduler is not None
257238 self ._start_ns = nanosecond_time ()
258- _scheduler .start_profiling ()
239+ self . scheduler .start_profiling ()
259240
260241 def __exit__ (self , ty , value , tb ):
261242 # type: (Optional[Any], Optional[Any], Optional[Any]) -> None
262- assert _scheduler is not None
263- _scheduler .stop_profiling ()
243+ self .scheduler .stop_profiling ()
264244 self ._stop_ns = nanosecond_time ()
265245
266246 def to_json (self , event_opt ):
267247 # type: (Any) -> Dict[str, Any]
268- assert _sample_buffer is not None
269248 assert self ._start_ns is not None
270249 assert self ._stop_ns is not None
271250
272251 return {
273252 "environment" : event_opt .get ("environment" ),
274253 "event_id" : uuid .uuid4 ().hex ,
275254 "platform" : "python" ,
276- "profile" : _sample_buffer .slice_profile (self ._start_ns , self ._stop_ns ),
255+ "profile" : self .scheduler .sample_buffer .slice_profile (
256+ self ._start_ns , self ._stop_ns
257+ ),
277258 "release" : event_opt .get ("release" , "" ),
278259 "timestamp" : event_opt ["timestamp" ],
279260 "version" : "1" ,
@@ -406,13 +387,36 @@ def slice_profile(self, start_ns, stop_ns):
406387 "thread_metadata" : thread_metadata ,
407388 }
408389
390+ def make_sampler (self ):
391+ # type: () -> Callable[..., None]
392+
393+ def _sample_stack (* args , ** kwargs ):
394+ # type: (*Any, **Any) -> None
395+ """
396+ Take a sample of the stack on all the threads in the process.
397+ This should be called at a regular interval to collect samples.
398+ """
399+
400+ self .write (
401+ (
402+ nanosecond_time (),
403+ [
404+ (tid , extract_stack (frame ))
405+ for tid , frame in sys ._current_frames ().items ()
406+ ],
407+ )
408+ )
409+
410+ return _sample_stack
411+
409412
410413class Scheduler (object ):
411414 mode = "unknown"
412415
413- def __init__ (self , sampler , frequency ):
414- # type: (Callable[..., None], int) -> None
415- self .sampler = sampler
416+ def __init__ (self , sample_buffer , frequency ):
417+ # type: (SampleBuffer, int) -> None
418+ self .sample_buffer = sample_buffer
419+ self .sampler = sample_buffer .make_sampler ()
416420 self ._lock = threading .Lock ()
417421 self ._count = 0
418422 self ._interval = 1.0 / frequency
@@ -447,9 +451,11 @@ class ThreadScheduler(Scheduler):
447451 mode = "thread"
448452 name = None # type: Optional[str]
449453
450- def __init__ (self , sampler , frequency ):
451- # type: (Callable[..., None], int) -> None
452- super (ThreadScheduler , self ).__init__ (sampler = sampler , frequency = frequency )
454+ def __init__ (self , sample_buffer , frequency ):
455+ # type: (SampleBuffer, int) -> None
456+ super (ThreadScheduler , self ).__init__ (
457+ sample_buffer = sample_buffer , frequency = frequency
458+ )
453459 self .stop_events = Queue ()
454460
455461 def setup (self ):
@@ -716,7 +722,8 @@ def start_profiling(transaction, hub=None):
716722
717723 # if profiling was not enabled, this should be a noop
718724 if _should_profile (transaction , hub ):
719- with Profile (transaction , hub = hub ):
725+ assert _scheduler is not None
726+ with Profile (_scheduler , transaction , hub = hub ):
720727 yield
721728 else :
722729 yield
0 commit comments