Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dynamic-sampling): adding org option for target sample rate #79090

Merged
22 changes: 22 additions & 0 deletions src/sentry/api/endpoints/organization_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
SAFE_FIELDS_DEFAULT,
SCRAPE_JAVASCRIPT_DEFAULT,
SENSITIVE_FIELDS_DEFAULT,
TARGET_SAMPLE_RATE_DEFAULT,
UPTIME_AUTODETECTION,
)
from sentry.datascrubbing import validate_pii_config_update, validate_pii_selectors
Expand Down Expand Up @@ -215,6 +216,7 @@
METRICS_ACTIVATE_LAST_FOR_GAUGES_DEFAULT,
),
("uptimeAutodetection", "sentry:uptime_autodetection", bool, UPTIME_AUTODETECTION),
("targetSampleRate", "sentry:target_sample_rate", float, TARGET_SAMPLE_RATE_DEFAULT),
)

DELETION_STATUSES = frozenset(
Expand Down Expand Up @@ -276,6 +278,7 @@ class OrganizationSerializer(BaseOrganizationSerializer):
relayPiiConfig = serializers.CharField(required=False, allow_blank=True, allow_null=True)
apdexThreshold = serializers.IntegerField(min_value=1, required=False)
uptimeAutodetection = serializers.BooleanField(required=False)
targetSampleRate = serializers.FloatField(required=False)

@cached_property
def _has_legacy_rate_limits(self):
Expand Down Expand Up @@ -365,6 +368,25 @@ def validate_projectRateLimit(self, value):
)
return value

def validate_targetSampleRate(self, value):
from sentry import features

organization = self.context["organization"]
request = self.context["request"]
has_dynamic_sampling_custom = features.has(
"organizations:dynamic-sampling-custom", organization, actor=request.user
)
if not has_dynamic_sampling_custom:
raise serializers.ValidationError(
"Organization does not have the custom dynamic sample rate feature enabled."
)

if not 0.0 <= value <= 1.0:
raise serializers.ValidationError(
"The targetSampleRate option must be in the range [0:1]"
)
return value

def validate(self, attrs):
attrs = super().validate(attrs)
if attrs.get("avatarType") == "upload":
Expand Down
1 change: 1 addition & 0 deletions src/sentry/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,7 @@ class InsightModules(Enum):
METRICS_ACTIVATE_LAST_FOR_GAUGES_DEFAULT = False
DATA_CONSENT_DEFAULT = False
UPTIME_AUTODETECTION = True
TARGET_SAMPLE_RATE_DEFAULT = 1.0

# `sentry:events_member_admin` - controls whether the 'member' role gets the event:admin scope
EVENTS_MEMBER_ADMIN_DEFAULT = True
Expand Down
33 changes: 32 additions & 1 deletion tests/sentry/api/endpoints/test_organization_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ def test_upload_avatar(self):
"sentry.integrations.github.integration.GitHubApiClient.get_repositories",
return_value=[{"name": "cool-repo", "full_name": "testgit/cool-repo"}],
)
@with_feature("organizations:codecov-integration")
@with_feature(["organizations:codecov-integration", "organizations:dynamic-sampling-custom"])
def test_various_options(self, mock_get_repositories):
initial = self.organization.get_audit_log_data()
with assume_test_silo_mode_of(AuditLogEntry):
Expand Down Expand Up @@ -455,6 +455,7 @@ def test_various_options(self, mock_get_repositories):
"metricsActivatePercentiles": False,
"metricsActivateLastForGauges": True,
"uptimeAutodetection": False,
"targetSampleRate": 0.1,
}

# needed to set require2FA
Expand Down Expand Up @@ -493,6 +494,7 @@ def test_various_options(self, mock_get_repositories):
assert options.get("sentry:metrics_activate_percentiles") is False
assert options.get("sentry:metrics_activate_last_for_gauges") is True
assert options.get("sentry:uptime_autodetection") is False
assert options.get("sentry:target_sample_rate") == 0.1

# log created
with assume_test_silo_mode_of(AuditLogEntry):
Expand Down Expand Up @@ -940,6 +942,35 @@ def test_org_mapping_already_taken(self):
self.create_organization(slug="taken")
self.get_error_response(self.organization.slug, slug="taken", status_code=400)

def test_target_sample_rate_feature(self):
with self.feature("organizations:dynamic-sampling-custom"):
data = {"targetSampleRate": 0.1}
self.get_success_response(self.organization.slug, **data)

with self.feature({"organizations:dynamic-sampling-custom": False}):
data = {"targetSampleRate": 0.1}
self.get_error_response(self.organization.slug, status_code=400, **data)

@with_feature("organizations:dynamic-sampling-custom")
def test_target_sample_rate_range(self):
# low, within and high
data = {"targetSampleRate": 0.0}
self.get_success_response(self.organization.slug, **data)

data = {"targetSampleRate": 0.1}
self.get_success_response(self.organization.slug, **data)

data = {"targetSampleRate": 1.0}
self.get_success_response(self.organization.slug, **data)

# below range
data = {"targetSampleRate": -0.1}
self.get_error_response(self.organization.slug, status_code=400, **data)

# above range
data = {"targetSampleRate": 1.1}
self.get_error_response(self.organization.slug, status_code=400, **data)


class OrganizationDeleteTest(OrganizationDetailsTestBase):
method = "delete"
Expand Down
Loading