27
27
from sentry_sdk ._types import TYPE_CHECKING
28
28
from sentry_sdk .utils import (
29
29
filename_for_module ,
30
+ is_valid_sample_rate ,
30
31
logger ,
31
32
nanosecond_time ,
32
33
set_in_app_in_frames ,
46
47
from typing_extensions import TypedDict
47
48
48
49
import sentry_sdk .tracing
49
- from sentry_sdk ._types import SamplingContext
50
+ from sentry_sdk ._types import SamplingContext , ProfilerMode
50
51
51
52
ThreadId = str
52
53
@@ -148,6 +149,23 @@ def is_gevent():
148
149
PROFILE_MINIMUM_SAMPLES = 2
149
150
150
151
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
+
151
169
def setup_profiler (options ):
152
170
# type: (Dict[str, Any]) -> bool
153
171
global _scheduler
@@ -171,7 +189,13 @@ def setup_profiler(options):
171
189
else :
172
190
default_profiler_mode = ThreadScheduler .mode
173
191
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
+ )
175
199
176
200
if (
177
201
profiler_mode == ThreadScheduler .mode
@@ -491,7 +515,13 @@ def _set_initial_sampling_decision(self, sampling_context):
491
515
return
492
516
493
517
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" )
495
525
496
526
# The profiles_sample_rate option was not set, so profiling
497
527
# was never enabled.
@@ -502,6 +532,13 @@ def _set_initial_sampling_decision(self, sampling_context):
502
532
self .sampled = False
503
533
return
504
534
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
+
505
542
# Now we roll the dice. random.random is inclusive of 0, but not of 1,
506
543
# so strict < is safe here. In case sample_rate is a boolean, cast it
507
544
# to a float (True becomes 1.0 and False becomes 0.0)
@@ -695,7 +732,7 @@ def valid(self):
695
732
696
733
697
734
class Scheduler (object ):
698
- mode = "unknown"
735
+ mode = "unknown" # type: ProfilerMode
699
736
700
737
def __init__ (self , frequency ):
701
738
# type: (int) -> None
@@ -824,7 +861,7 @@ class ThreadScheduler(Scheduler):
824
861
the sampler at a regular interval.
825
862
"""
826
863
827
- mode = "thread"
864
+ mode = "thread" # type: ProfilerMode
828
865
name = "sentry.profiler.ThreadScheduler"
829
866
830
867
def __init__ (self , frequency ):
@@ -905,7 +942,7 @@ class GeventScheduler(Scheduler):
905
942
results in a sample containing only the sampler's code.
906
943
"""
907
944
908
- mode = "gevent"
945
+ mode = "gevent" # type: ProfilerMode
909
946
name = "sentry.profiler.GeventScheduler"
910
947
911
948
def __init__ (self , frequency ):
0 commit comments