Skip to content

Commit 80d0b27

Browse files
constantiniuscmanallen
authored andcommitted
feat(dynamic-sampling): adding org option for target sample rate (#79090)
1 parent c82ffeb commit 80d0b27

File tree

3 files changed

+55
-1
lines changed

3 files changed

+55
-1
lines changed

src/sentry/api/endpoints/organization_details.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
SAFE_FIELDS_DEFAULT,
6363
SCRAPE_JAVASCRIPT_DEFAULT,
6464
SENSITIVE_FIELDS_DEFAULT,
65+
TARGET_SAMPLE_RATE_DEFAULT,
6566
UPTIME_AUTODETECTION,
6667
)
6768
from sentry.datascrubbing import validate_pii_config_update, validate_pii_selectors
@@ -215,6 +216,7 @@
215216
METRICS_ACTIVATE_LAST_FOR_GAUGES_DEFAULT,
216217
),
217218
("uptimeAutodetection", "sentry:uptime_autodetection", bool, UPTIME_AUTODETECTION),
219+
("targetSampleRate", "sentry:target_sample_rate", float, TARGET_SAMPLE_RATE_DEFAULT),
218220
)
219221

220222
DELETION_STATUSES = frozenset(
@@ -276,6 +278,7 @@ class OrganizationSerializer(BaseOrganizationSerializer):
276278
relayPiiConfig = serializers.CharField(required=False, allow_blank=True, allow_null=True)
277279
apdexThreshold = serializers.IntegerField(min_value=1, required=False)
278280
uptimeAutodetection = serializers.BooleanField(required=False)
281+
targetSampleRate = serializers.FloatField(required=False)
279282

280283
@cached_property
281284
def _has_legacy_rate_limits(self):
@@ -365,6 +368,25 @@ def validate_projectRateLimit(self, value):
365368
)
366369
return value
367370

371+
def validate_targetSampleRate(self, value):
372+
from sentry import features
373+
374+
organization = self.context["organization"]
375+
request = self.context["request"]
376+
has_dynamic_sampling_custom = features.has(
377+
"organizations:dynamic-sampling-custom", organization, actor=request.user
378+
)
379+
if not has_dynamic_sampling_custom:
380+
raise serializers.ValidationError(
381+
"Organization does not have the custom dynamic sample rate feature enabled."
382+
)
383+
384+
if not 0.0 <= value <= 1.0:
385+
raise serializers.ValidationError(
386+
"The targetSampleRate option must be in the range [0:1]"
387+
)
388+
return value
389+
368390
def validate(self, attrs):
369391
attrs = super().validate(attrs)
370392
if attrs.get("avatarType") == "upload":

src/sentry/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,7 @@ class InsightModules(Enum):
710710
METRICS_ACTIVATE_LAST_FOR_GAUGES_DEFAULT = False
711711
DATA_CONSENT_DEFAULT = False
712712
UPTIME_AUTODETECTION = True
713+
TARGET_SAMPLE_RATE_DEFAULT = 1.0
713714

714715
# `sentry:events_member_admin` - controls whether the 'member' role gets the event:admin scope
715716
EVENTS_MEMBER_ADMIN_DEFAULT = True

tests/sentry/api/endpoints/test_organization_details.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ def test_upload_avatar(self):
410410
"sentry.integrations.github.integration.GitHubApiClient.get_repositories",
411411
return_value=[{"name": "cool-repo", "full_name": "testgit/cool-repo"}],
412412
)
413-
@with_feature("organizations:codecov-integration")
413+
@with_feature(["organizations:codecov-integration", "organizations:dynamic-sampling-custom"])
414414
def test_various_options(self, mock_get_repositories):
415415
initial = self.organization.get_audit_log_data()
416416
with assume_test_silo_mode_of(AuditLogEntry):
@@ -455,6 +455,7 @@ def test_various_options(self, mock_get_repositories):
455455
"metricsActivatePercentiles": False,
456456
"metricsActivateLastForGauges": True,
457457
"uptimeAutodetection": False,
458+
"targetSampleRate": 0.1,
458459
}
459460

460461
# needed to set require2FA
@@ -493,6 +494,7 @@ def test_various_options(self, mock_get_repositories):
493494
assert options.get("sentry:metrics_activate_percentiles") is False
494495
assert options.get("sentry:metrics_activate_last_for_gauges") is True
495496
assert options.get("sentry:uptime_autodetection") is False
497+
assert options.get("sentry:target_sample_rate") == 0.1
496498

497499
# log created
498500
with assume_test_silo_mode_of(AuditLogEntry):
@@ -940,6 +942,35 @@ def test_org_mapping_already_taken(self):
940942
self.create_organization(slug="taken")
941943
self.get_error_response(self.organization.slug, slug="taken", status_code=400)
942944

945+
def test_target_sample_rate_feature(self):
946+
with self.feature("organizations:dynamic-sampling-custom"):
947+
data = {"targetSampleRate": 0.1}
948+
self.get_success_response(self.organization.slug, **data)
949+
950+
with self.feature({"organizations:dynamic-sampling-custom": False}):
951+
data = {"targetSampleRate": 0.1}
952+
self.get_error_response(self.organization.slug, status_code=400, **data)
953+
954+
@with_feature("organizations:dynamic-sampling-custom")
955+
def test_target_sample_rate_range(self):
956+
# low, within and high
957+
data = {"targetSampleRate": 0.0}
958+
self.get_success_response(self.organization.slug, **data)
959+
960+
data = {"targetSampleRate": 0.1}
961+
self.get_success_response(self.organization.slug, **data)
962+
963+
data = {"targetSampleRate": 1.0}
964+
self.get_success_response(self.organization.slug, **data)
965+
966+
# below range
967+
data = {"targetSampleRate": -0.1}
968+
self.get_error_response(self.organization.slug, status_code=400, **data)
969+
970+
# above range
971+
data = {"targetSampleRate": 1.1}
972+
self.get_error_response(self.organization.slug, status_code=400, **data)
973+
943974

944975
class OrganizationDeleteTest(OrganizationDetailsTestBase):
945976
method = "delete"

0 commit comments

Comments
 (0)