2727from sentry_sdk ._types import TYPE_CHECKING
2828from sentry_sdk .utils import (
2929 filename_for_module ,
30+ is_valid_sample_rate ,
3031 logger ,
3132 nanosecond_time ,
3233 set_in_app_in_frames ,
4647 from typing_extensions import TypedDict
4748
4849 import sentry_sdk .tracing
49- from sentry_sdk ._types import SamplingContext
50+ from sentry_sdk ._types import SamplingContext , ProfilerMode
5051
5152 ThreadId = str
5253
@@ -148,6 +149,23 @@ def is_gevent():
148149PROFILE_MINIMUM_SAMPLES = 2
149150
150151
152+ def has_profiling_enabled (options ):
153+ # type: (Dict[str, Any]) -> bool
154+ profiles_sampler = options ["profiles_sampler" ]
155+ if profiles_sampler is not None :
156+ return True
157+
158+ profiles_sample_rate = options ["profiles_sample_rate" ]
159+ if profiles_sample_rate is not None and profiles_sample_rate > 0 :
160+ return True
161+
162+ profiles_sample_rate = options ["_experiments" ].get ("profiles_sample_rate" )
163+ if profiles_sample_rate is not None and profiles_sample_rate > 0 :
164+ return True
165+
166+ return False
167+
168+
151169def setup_profiler (options ):
152170 # type: (Dict[str, Any]) -> bool
153171 global _scheduler
@@ -171,7 +189,13 @@ def setup_profiler(options):
171189 else :
172190 default_profiler_mode = ThreadScheduler .mode
173191
174- profiler_mode = options ["_experiments" ].get ("profiler_mode" , default_profiler_mode )
192+ if options .get ("profiler_mode" ) is not None :
193+ profiler_mode = options ["profiler_mode" ]
194+ else :
195+ profiler_mode = (
196+ options .get ("_experiments" , {}).get ("profiler_mode" )
197+ or default_profiler_mode
198+ )
175199
176200 if (
177201 profiler_mode == ThreadScheduler .mode
@@ -491,7 +515,13 @@ def _set_initial_sampling_decision(self, sampling_context):
491515 return
492516
493517 options = client .options
494- sample_rate = options ["_experiments" ].get ("profiles_sample_rate" )
518+
519+ if callable (options .get ("profiles_sampler" )):
520+ sample_rate = options ["profiles_sampler" ](sampling_context )
521+ elif options ["profiles_sample_rate" ] is not None :
522+ sample_rate = options ["profiles_sample_rate" ]
523+ else :
524+ sample_rate = options ["_experiments" ].get ("profiles_sample_rate" )
495525
496526 # The profiles_sample_rate option was not set, so profiling
497527 # was never enabled.
@@ -502,6 +532,13 @@ def _set_initial_sampling_decision(self, sampling_context):
502532 self .sampled = False
503533 return
504534
535+ if not is_valid_sample_rate (sample_rate , source = "Profiling" ):
536+ logger .warning (
537+ "[Profiling] Discarding profile because of invalid sample rate."
538+ )
539+ self .sampled = False
540+ return
541+
505542 # Now we roll the dice. random.random is inclusive of 0, but not of 1,
506543 # so strict < is safe here. In case sample_rate is a boolean, cast it
507544 # to a float (True becomes 1.0 and False becomes 0.0)
@@ -695,7 +732,7 @@ def valid(self):
695732
696733
697734class Scheduler (object ):
698- mode = "unknown"
735+ mode = "unknown" # type: ProfilerMode
699736
700737 def __init__ (self , frequency ):
701738 # type: (int) -> None
@@ -824,7 +861,7 @@ class ThreadScheduler(Scheduler):
824861 the sampler at a regular interval.
825862 """
826863
827- mode = "thread"
864+ mode = "thread" # type: ProfilerMode
828865 name = "sentry.profiler.ThreadScheduler"
829866
830867 def __init__ (self , frequency ):
@@ -905,7 +942,7 @@ class GeventScheduler(Scheduler):
905942 results in a sample containing only the sampler's code.
906943 """
907944
908- mode = "gevent"
945+ mode = "gevent" # type: ProfilerMode
909946 name = "sentry.profiler.GeventScheduler"
910947
911948 def __init__ (self , frequency ):
0 commit comments