Skip to content

Commit 3a32207

Browse files
authored
chore(perf-issues): Remove GA'd/unusued project admin settings (#92116)
Also refactors some of the Internal <-> Configurable settings for performance detectors since the toggling is user facing now.
1 parent 94d21bb commit 3a32207

File tree

5 files changed

+85
-73
lines changed

5 files changed

+85
-73
lines changed

src/sentry/api/endpoints/project_details.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,6 @@ class ProjectMemberSerializer(serializers.Serializer):
128128
"copy_from_project",
129129
"targetSampleRate",
130130
"dynamicSamplingBiases",
131-
"performanceIssueCreationRate",
132-
"performanceIssueCreationThroughPlatform",
133-
"performanceIssueSendToPlatform",
134131
"tempestFetchScreenshots",
135132
"tempestFetchDumps",
136133
"autofixAutomationTuning",
@@ -223,9 +220,6 @@ class ProjectAdminSerializer(ProjectMemberSerializer):
223220
copy_from_project = serializers.IntegerField(required=False)
224221
targetSampleRate = serializers.FloatField(required=False, min_value=0, max_value=1)
225222
dynamicSamplingBiases = DynamicSamplingBiasSerializer(required=False, many=True)
226-
performanceIssueCreationRate = serializers.FloatField(required=False, min_value=0, max_value=1)
227-
performanceIssueCreationThroughPlatform = serializers.BooleanField(required=False)
228-
performanceIssueSendToPlatform = serializers.BooleanField(required=False)
229223
tempestFetchScreenshots = serializers.BooleanField(required=False)
230224
tempestFetchDumps = serializers.BooleanField(required=False)
231225
autofixAutomationTuning = serializers.ChoiceField(

src/sentry/api/endpoints/project_performance_issue_settings.py

Lines changed: 78 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -34,72 +34,84 @@
3434
SETTINGS_PROJECT_OPTION_KEY = "sentry:performance_issue_settings"
3535

3636

37-
# These options should only be accessible internally and used by
38-
# support to enable/disable performance issue detection for an outlying project
39-
# on a case-by-case basis.
4037
class InternalProjectOptions(Enum):
41-
N_PLUS_ONE_DB = "n_plus_one_db_queries_detection_enabled"
42-
UNCOMPRESSED_ASSET = "uncompressed_assets_detection_enabled"
43-
CONSECUTIVE_HTTP_SPANS = "consecutive_http_spans_detection_enabled"
44-
LARGE_HTTP_PAYLOAD = "large_http_payload_detection_enabled"
45-
N_PLUS_ONE_API_CALLS = "n_plus_one_api_calls_detection_enabled"
46-
DB_ON_MAIN_THREAD = "db_on_main_thread_detection_enabled"
47-
FILE_IO_MAIN_THREAD = "file_io_on_main_thread_detection_enabled"
48-
CONSECUTIVE_DB_QUERIES = "consecutive_db_queries_detection_enabled"
49-
RENDER_BLOCKING_ASSET = "large_render_blocking_asset_detection_enabled"
50-
SLOW_DB_QUERY = "slow_db_queries_detection_enabled"
51-
HTTP_OVERHEAD = "http_overhead_detection_enabled"
38+
"""
39+
Settings that are only accessible to superusers.
40+
"""
41+
5242
TRANSACTION_DURATION_REGRESSION = "transaction_duration_regression_detection_enabled"
5343
FUNCTION_DURATION_REGRESSION = "function_duration_regression_detection_enabled"
5444

5545

5646
class ConfigurableThresholds(Enum):
47+
"""
48+
All the settings that can be configured by users with the appropriate permissions.
49+
"""
50+
51+
N_PLUS_ONE_DB = "n_plus_one_db_queries_detection_enabled"
5752
N_PLUS_ONE_DB_DURATION = "n_plus_one_db_duration_threshold"
5853
N_PLUS_ONE_DB_COUNT = "n_plus_one_db_count"
54+
UNCOMPRESSED_ASSET = "uncompressed_assets_detection_enabled"
5955
UNCOMPRESSED_ASSET_DURATION = "uncompressed_asset_duration_threshold"
6056
UNCOMPRESSED_ASSET_SIZE = "uncompressed_asset_size_threshold"
57+
LARGE_HTTP_PAYLOAD = "large_http_payload_detection_enabled"
6158
LARGE_HTTP_PAYLOAD_SIZE = "large_http_payload_size_threshold"
59+
DB_ON_MAIN_THREAD = "db_on_main_thread_detection_enabled"
6260
DB_ON_MAIN_THREAD_DURATION = "db_on_main_thread_duration_threshold"
61+
FILE_IO_MAIN_THREAD = "file_io_on_main_thread_detection_enabled"
6362
FILE_IO_MAIN_THREAD_DURATION = "file_io_on_main_thread_duration_threshold"
63+
CONSECUTIVE_DB_QUERIES = "consecutive_db_queries_detection_enabled"
6464
CONSECUTIVE_DB_QUERIES_MIN_TIME_SAVED = "consecutive_db_min_time_saved_threshold"
65+
RENDER_BLOCKING_ASSET = "large_render_blocking_asset_detection_enabled"
6566
RENDER_BLOCKING_ASSET_FCP_RATIO = "render_blocking_fcp_ratio"
67+
SLOW_DB_QUERY = "slow_db_queries_detection_enabled"
6668
SLOW_DB_QUERY_DURATION = "slow_db_query_duration_threshold"
69+
N_PLUS_ONE_API_CALLS = "n_plus_one_api_calls_detection_enabled"
6770
N_PLUS_API_CALLS_DURATION = "n_plus_one_api_calls_total_duration_threshold"
71+
CONSECUTIVE_HTTP_SPANS = "consecutive_http_spans_detection_enabled"
6872
CONSECUTIVE_HTTP_SPANS_MIN_TIME_SAVED = "consecutive_http_spans_min_time_saved_threshold"
73+
HTTP_OVERHEAD = "http_overhead_detection_enabled"
6974
HTTP_OVERHEAD_REQUEST_DELAY = "http_request_delay_threshold"
7075

7176

72-
internal_only_project_settings_to_group_map: dict[str, type[GroupType]] = {
73-
InternalProjectOptions.UNCOMPRESSED_ASSET.value: PerformanceUncompressedAssetsGroupType,
74-
InternalProjectOptions.CONSECUTIVE_HTTP_SPANS.value: PerformanceConsecutiveHTTPQueriesGroupType,
75-
InternalProjectOptions.LARGE_HTTP_PAYLOAD.value: PerformanceLargeHTTPPayloadGroupType,
76-
InternalProjectOptions.N_PLUS_ONE_DB.value: PerformanceNPlusOneGroupType,
77-
InternalProjectOptions.N_PLUS_ONE_API_CALLS.value: PerformanceNPlusOneAPICallsGroupType,
78-
InternalProjectOptions.DB_ON_MAIN_THREAD.value: PerformanceDBMainThreadGroupType,
79-
InternalProjectOptions.FILE_IO_MAIN_THREAD.value: PerformanceFileIOMainThreadGroupType,
80-
InternalProjectOptions.CONSECUTIVE_DB_QUERIES.value: PerformanceConsecutiveDBQueriesGroupType,
81-
InternalProjectOptions.RENDER_BLOCKING_ASSET.value: PerformanceRenderBlockingAssetSpanGroupType,
82-
InternalProjectOptions.SLOW_DB_QUERY.value: PerformanceSlowDBQueryGroupType,
83-
InternalProjectOptions.HTTP_OVERHEAD.value: PerformanceHTTPOverheadGroupType,
77+
project_settings_to_group_map: dict[str, type[GroupType]] = {
78+
ConfigurableThresholds.UNCOMPRESSED_ASSET.value: PerformanceUncompressedAssetsGroupType,
79+
ConfigurableThresholds.CONSECUTIVE_HTTP_SPANS.value: PerformanceConsecutiveHTTPQueriesGroupType,
80+
ConfigurableThresholds.LARGE_HTTP_PAYLOAD.value: PerformanceLargeHTTPPayloadGroupType,
81+
ConfigurableThresholds.N_PLUS_ONE_DB.value: PerformanceNPlusOneGroupType,
82+
ConfigurableThresholds.N_PLUS_ONE_API_CALLS.value: PerformanceNPlusOneAPICallsGroupType,
83+
ConfigurableThresholds.DB_ON_MAIN_THREAD.value: PerformanceDBMainThreadGroupType,
84+
ConfigurableThresholds.FILE_IO_MAIN_THREAD.value: PerformanceFileIOMainThreadGroupType,
85+
ConfigurableThresholds.CONSECUTIVE_DB_QUERIES.value: PerformanceConsecutiveDBQueriesGroupType,
86+
ConfigurableThresholds.RENDER_BLOCKING_ASSET.value: PerformanceRenderBlockingAssetSpanGroupType,
87+
ConfigurableThresholds.SLOW_DB_QUERY.value: PerformanceSlowDBQueryGroupType,
88+
ConfigurableThresholds.HTTP_OVERHEAD.value: PerformanceHTTPOverheadGroupType,
8489
InternalProjectOptions.TRANSACTION_DURATION_REGRESSION.value: PerformanceP95EndpointRegressionGroupType,
8590
InternalProjectOptions.FUNCTION_DURATION_REGRESSION.value: ProfileFunctionRegressionType,
8691
}
87-
88-
configurable_thresholds_to_internal_settings_map: dict[str, str] = {
89-
ConfigurableThresholds.N_PLUS_ONE_DB_DURATION.value: InternalProjectOptions.N_PLUS_ONE_DB.value,
90-
ConfigurableThresholds.N_PLUS_ONE_DB_COUNT.value: InternalProjectOptions.N_PLUS_ONE_DB.value,
91-
ConfigurableThresholds.UNCOMPRESSED_ASSET_DURATION.value: InternalProjectOptions.UNCOMPRESSED_ASSET.value,
92-
ConfigurableThresholds.UNCOMPRESSED_ASSET_SIZE.value: InternalProjectOptions.UNCOMPRESSED_ASSET.value,
93-
ConfigurableThresholds.LARGE_HTTP_PAYLOAD_SIZE.value: InternalProjectOptions.LARGE_HTTP_PAYLOAD.value,
94-
ConfigurableThresholds.DB_ON_MAIN_THREAD_DURATION.value: InternalProjectOptions.DB_ON_MAIN_THREAD.value,
95-
ConfigurableThresholds.FILE_IO_MAIN_THREAD_DURATION.value: InternalProjectOptions.FILE_IO_MAIN_THREAD.value,
96-
ConfigurableThresholds.CONSECUTIVE_DB_QUERIES_MIN_TIME_SAVED.value: InternalProjectOptions.CONSECUTIVE_DB_QUERIES.value,
97-
ConfigurableThresholds.RENDER_BLOCKING_ASSET_FCP_RATIO.value: InternalProjectOptions.RENDER_BLOCKING_ASSET.value,
98-
ConfigurableThresholds.SLOW_DB_QUERY_DURATION.value: InternalProjectOptions.SLOW_DB_QUERY.value,
99-
ConfigurableThresholds.N_PLUS_API_CALLS_DURATION.value: InternalProjectOptions.N_PLUS_ONE_API_CALLS.value,
100-
ConfigurableThresholds.CONSECUTIVE_HTTP_SPANS_MIN_TIME_SAVED.value: InternalProjectOptions.CONSECUTIVE_HTTP_SPANS.value,
101-
ConfigurableThresholds.HTTP_OVERHEAD_REQUEST_DELAY.value: InternalProjectOptions.HTTP_OVERHEAD.value,
92+
"""
93+
A mapping of the management settings to the group type that the detector spawns.
94+
"""
95+
96+
thresholds_to_manage_map: dict[str, str] = {
97+
ConfigurableThresholds.N_PLUS_ONE_DB_DURATION.value: ConfigurableThresholds.N_PLUS_ONE_DB.value,
98+
ConfigurableThresholds.N_PLUS_ONE_DB_COUNT.value: ConfigurableThresholds.N_PLUS_ONE_DB.value,
99+
ConfigurableThresholds.UNCOMPRESSED_ASSET_DURATION.value: ConfigurableThresholds.UNCOMPRESSED_ASSET.value,
100+
ConfigurableThresholds.UNCOMPRESSED_ASSET_SIZE.value: ConfigurableThresholds.UNCOMPRESSED_ASSET.value,
101+
ConfigurableThresholds.LARGE_HTTP_PAYLOAD_SIZE.value: ConfigurableThresholds.LARGE_HTTP_PAYLOAD.value,
102+
ConfigurableThresholds.DB_ON_MAIN_THREAD_DURATION.value: ConfigurableThresholds.DB_ON_MAIN_THREAD.value,
103+
ConfigurableThresholds.FILE_IO_MAIN_THREAD_DURATION.value: ConfigurableThresholds.FILE_IO_MAIN_THREAD.value,
104+
ConfigurableThresholds.CONSECUTIVE_DB_QUERIES_MIN_TIME_SAVED.value: ConfigurableThresholds.CONSECUTIVE_DB_QUERIES.value,
105+
ConfigurableThresholds.RENDER_BLOCKING_ASSET_FCP_RATIO.value: ConfigurableThresholds.RENDER_BLOCKING_ASSET.value,
106+
ConfigurableThresholds.SLOW_DB_QUERY_DURATION.value: ConfigurableThresholds.SLOW_DB_QUERY.value,
107+
ConfigurableThresholds.N_PLUS_API_CALLS_DURATION.value: ConfigurableThresholds.N_PLUS_ONE_API_CALLS.value,
108+
ConfigurableThresholds.CONSECUTIVE_HTTP_SPANS_MIN_TIME_SAVED.value: ConfigurableThresholds.CONSECUTIVE_HTTP_SPANS.value,
109+
ConfigurableThresholds.HTTP_OVERHEAD_REQUEST_DELAY.value: ConfigurableThresholds.HTTP_OVERHEAD.value,
102110
}
111+
"""
112+
A mapping of threshold setting to the parent setting that manages it's detection.
113+
Used to determine if a threshold setting can be modified.
114+
"""
103115

104116

105117
class ProjectPerformanceIssueSettingsSerializer(serializers.Serializer):
@@ -155,15 +167,26 @@ class ProjectPerformanceIssueSettingsSerializer(serializers.Serializer):
155167
function_duration_regression_detection_enabled = serializers.BooleanField(required=False)
156168

157169

170+
def get_management_options() -> list[str]:
171+
"""
172+
Returns the option keys that control whether a performance issue detector is enabled.
173+
"""
174+
return [
175+
*[setting.value for setting in InternalProjectOptions],
176+
*list(thresholds_to_manage_map.values()),
177+
]
178+
179+
158180
def get_disabled_threshold_options(payload, current_settings):
181+
"""
182+
Returns the option keys that are disabled, based on the current management settings.
183+
"""
159184
options = []
160-
internal_only_settings = [setting.value for setting in InternalProjectOptions]
185+
management_options = get_management_options()
161186
for option in payload:
162-
if option not in internal_only_settings:
163-
internal_setting_for_option = configurable_thresholds_to_internal_settings_map.get(
164-
option
165-
)
166-
is_threshold_enabled = current_settings.get(internal_setting_for_option)
187+
if option not in management_options:
188+
manage_detector_setting = thresholds_to_manage_map.get(option)
189+
is_threshold_enabled = current_settings.get(manage_detector_setting)
167190
if not is_threshold_enabled:
168191
options.append(option)
169192
return options
@@ -222,15 +245,7 @@ def put(self, request: Request, project) -> Response:
222245
)
223246

224247
body_has_admin_options = any([option in request.data for option in internal_only_settings])
225-
if (
226-
body_has_admin_options
227-
and not superuser_has_permission(request)
228-
and not features.has(
229-
"organizations:performance-manage-detectors",
230-
project.organization,
231-
actor=request.user,
232-
)
233-
):
248+
if body_has_admin_options and not superuser_has_permission(request):
234249
return Response(
235250
{
236251
"detail": {
@@ -241,6 +256,9 @@ def put(self, request: Request, project) -> Response:
241256
status=status.HTTP_403_FORBIDDEN,
242257
)
243258

259+
body_has_management_options = any(
260+
[option in get_management_options() for option in request.data]
261+
)
244262
serializer = ProjectPerformanceIssueSettingsSerializer(data=request.data)
245263

246264
if not serializer.is_valid():
@@ -273,7 +291,7 @@ def put(self, request: Request, project) -> Response:
273291
{**performance_issue_settings_default, **performance_issue_settings, **data},
274292
)
275293

276-
if body_has_admin_options:
294+
if body_has_admin_options or body_has_management_options:
277295
self.create_audit_entry(
278296
request=self.request,
279297
actor=request.user,
@@ -290,16 +308,16 @@ def delete(self, request: Request, project) -> Response:
290308
return self.respond(status=status.HTTP_404_NOT_FOUND)
291309

292310
project_settings = project.get_option(SETTINGS_PROJECT_OPTION_KEY, default={})
311+
management_options = get_management_options()
293312
threshold_options = [setting.value for setting in ConfigurableThresholds]
294-
internal_only_settings = [setting.value for setting in InternalProjectOptions]
295313
disabled_options = get_disabled_threshold_options(threshold_options, project_settings)
296314

297315
if project_settings:
298316
unchanged_options = (
299-
{ # internal settings and disabled threshold settings can not be reset
317+
{ # Management settings and disabled threshold settings can not be reset
300318
option: project_settings[option]
301319
for option in project_settings
302-
if option in internal_only_settings or option in disabled_options
320+
if option in management_options or option in disabled_options
303321
}
304322
)
305323
project.update_option(SETTINGS_PROJECT_OPTION_KEY, unchanged_options)

src/sentry/audit_log/events.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def __init__(self):
181181

182182
def render(self, audit_log_entry: AuditLogEntry):
183183
from sentry.api.endpoints.project_performance_issue_settings import (
184-
internal_only_project_settings_to_group_map as map,
184+
project_settings_to_group_map as map,
185185
)
186186

187187
data = audit_log_entry.data

src/sentry/utils/performance_issues/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ There are quite a few places which need to be updated when adding a new performa
5252
- [ ] If your value is not customizable, add it to the dictionary
5353
- [ ] If it is customizable, access it via `settings[key_name]`
5454
- [ ] Then add it to [project_performance_issue_settings.py](../../api/endpoints/project_performance_issue_settings.py), either `InternalProjectOptions` or `ConfigurableThresholds`
55-
- [ ] In the same file, Add it to the map, either `internal_only_project_settings_to_group_map` or `configurable_thresholds_to_internal_settings_map` using the new GroupType
55+
- [ ] In the same file, Add it to the mappings `project_settings_to_group_map`/`thresholds_to_manage_map` using the new GroupType
5656
- [ ] In the same file, Add a serializer field to `ProjectPerformanceIssueSettingsSerializer` to allow it to be validated from the inbound API.
5757
- [ ] (Optional) The frontend file (`projectPerformance.tsx`) should add the new field.
5858
- [ ] Then, to set a default value, register an option in [defaults.py](../../options/defaults.py)

tests/sentry/api/endpoints/test_project_performance_issue_settings.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ def test_put_non_super_user_updates_detection_setting(self):
149149
response = self.get_error_response(
150150
self.project.organization.slug,
151151
self.project.slug,
152-
n_plus_one_db_queries_detection_enabled=False,
152+
transaction_duration_regression_detection_enabled=False,
153153
method="put",
154154
status_code=status.HTTP_403_FORBIDDEN,
155155
)
@@ -187,7 +187,7 @@ def test_put_superuser_read_write_updates_detection_setting(self):
187187
self.get_error_response(
188188
self.project.organization.slug,
189189
self.project.slug,
190-
n_plus_one_db_queries_detection_enabled=False,
190+
function_duration_regression_detection_enabled=False,
191191
method="put",
192192
status_code=status.HTTP_403_FORBIDDEN,
193193
)
@@ -198,18 +198,18 @@ def test_put_superuser_read_write_updates_detection_setting(self):
198198
response = self.get_success_response(
199199
self.project.organization.slug,
200200
self.project.slug,
201-
n_plus_one_db_queries_detection_enabled=False,
201+
function_duration_regression_detection_enabled=False,
202202
method="put",
203203
status_code=status.HTTP_200_OK,
204204
)
205205

206-
assert not response.data["n_plus_one_db_queries_detection_enabled"]
206+
assert not response.data["function_duration_regression_detection_enabled"]
207207

208208
get_response = self.get_success_response(
209209
self.project.organization.slug, self.project.slug, format="json"
210210
)
211211

212-
assert not get_response.data["n_plus_one_db_queries_detection_enabled"]
212+
assert not get_response.data["function_duration_regression_detection_enabled"]
213213

214214
@with_feature("organizations:performance-view")
215215
def test_put_update_non_super_user_option(self):

0 commit comments

Comments
 (0)