34
34
SETTINGS_PROJECT_OPTION_KEY = "sentry:performance_issue_settings"
35
35
36
36
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.
40
37
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
+
52
42
TRANSACTION_DURATION_REGRESSION = "transaction_duration_regression_detection_enabled"
53
43
FUNCTION_DURATION_REGRESSION = "function_duration_regression_detection_enabled"
54
44
55
45
56
46
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"
57
52
N_PLUS_ONE_DB_DURATION = "n_plus_one_db_duration_threshold"
58
53
N_PLUS_ONE_DB_COUNT = "n_plus_one_db_count"
54
+ UNCOMPRESSED_ASSET = "uncompressed_assets_detection_enabled"
59
55
UNCOMPRESSED_ASSET_DURATION = "uncompressed_asset_duration_threshold"
60
56
UNCOMPRESSED_ASSET_SIZE = "uncompressed_asset_size_threshold"
57
+ LARGE_HTTP_PAYLOAD = "large_http_payload_detection_enabled"
61
58
LARGE_HTTP_PAYLOAD_SIZE = "large_http_payload_size_threshold"
59
+ DB_ON_MAIN_THREAD = "db_on_main_thread_detection_enabled"
62
60
DB_ON_MAIN_THREAD_DURATION = "db_on_main_thread_duration_threshold"
61
+ FILE_IO_MAIN_THREAD = "file_io_on_main_thread_detection_enabled"
63
62
FILE_IO_MAIN_THREAD_DURATION = "file_io_on_main_thread_duration_threshold"
63
+ CONSECUTIVE_DB_QUERIES = "consecutive_db_queries_detection_enabled"
64
64
CONSECUTIVE_DB_QUERIES_MIN_TIME_SAVED = "consecutive_db_min_time_saved_threshold"
65
+ RENDER_BLOCKING_ASSET = "large_render_blocking_asset_detection_enabled"
65
66
RENDER_BLOCKING_ASSET_FCP_RATIO = "render_blocking_fcp_ratio"
67
+ SLOW_DB_QUERY = "slow_db_queries_detection_enabled"
66
68
SLOW_DB_QUERY_DURATION = "slow_db_query_duration_threshold"
69
+ N_PLUS_ONE_API_CALLS = "n_plus_one_api_calls_detection_enabled"
67
70
N_PLUS_API_CALLS_DURATION = "n_plus_one_api_calls_total_duration_threshold"
71
+ CONSECUTIVE_HTTP_SPANS = "consecutive_http_spans_detection_enabled"
68
72
CONSECUTIVE_HTTP_SPANS_MIN_TIME_SAVED = "consecutive_http_spans_min_time_saved_threshold"
73
+ HTTP_OVERHEAD = "http_overhead_detection_enabled"
69
74
HTTP_OVERHEAD_REQUEST_DELAY = "http_request_delay_threshold"
70
75
71
76
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 ,
84
89
InternalProjectOptions .TRANSACTION_DURATION_REGRESSION .value : PerformanceP95EndpointRegressionGroupType ,
85
90
InternalProjectOptions .FUNCTION_DURATION_REGRESSION .value : ProfileFunctionRegressionType ,
86
91
}
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 ,
102
110
}
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
+ """
103
115
104
116
105
117
class ProjectPerformanceIssueSettingsSerializer (serializers .Serializer ):
@@ -155,15 +167,26 @@ class ProjectPerformanceIssueSettingsSerializer(serializers.Serializer):
155
167
function_duration_regression_detection_enabled = serializers .BooleanField (required = False )
156
168
157
169
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
+
158
180
def get_disabled_threshold_options (payload , current_settings ):
181
+ """
182
+ Returns the option keys that are disabled, based on the current management settings.
183
+ """
159
184
options = []
160
- internal_only_settings = [ setting . value for setting in InternalProjectOptions ]
185
+ management_options = get_management_options ()
161
186
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 )
167
190
if not is_threshold_enabled :
168
191
options .append (option )
169
192
return options
@@ -222,15 +245,7 @@ def put(self, request: Request, project) -> Response:
222
245
)
223
246
224
247
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 ):
234
249
return Response (
235
250
{
236
251
"detail" : {
@@ -241,6 +256,9 @@ def put(self, request: Request, project) -> Response:
241
256
status = status .HTTP_403_FORBIDDEN ,
242
257
)
243
258
259
+ body_has_management_options = any (
260
+ [option in get_management_options () for option in request .data ]
261
+ )
244
262
serializer = ProjectPerformanceIssueSettingsSerializer (data = request .data )
245
263
246
264
if not serializer .is_valid ():
@@ -273,7 +291,7 @@ def put(self, request: Request, project) -> Response:
273
291
{** performance_issue_settings_default , ** performance_issue_settings , ** data },
274
292
)
275
293
276
- if body_has_admin_options :
294
+ if body_has_admin_options or body_has_management_options :
277
295
self .create_audit_entry (
278
296
request = self .request ,
279
297
actor = request .user ,
@@ -290,16 +308,16 @@ def delete(self, request: Request, project) -> Response:
290
308
return self .respond (status = status .HTTP_404_NOT_FOUND )
291
309
292
310
project_settings = project .get_option (SETTINGS_PROJECT_OPTION_KEY , default = {})
311
+ management_options = get_management_options ()
293
312
threshold_options = [setting .value for setting in ConfigurableThresholds ]
294
- internal_only_settings = [setting .value for setting in InternalProjectOptions ]
295
313
disabled_options = get_disabled_threshold_options (threshold_options , project_settings )
296
314
297
315
if project_settings :
298
316
unchanged_options = (
299
- { # internal settings and disabled threshold settings can not be reset
317
+ { # Management settings and disabled threshold settings can not be reset
300
318
option : project_settings [option ]
301
319
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
303
321
}
304
322
)
305
323
project .update_option (SETTINGS_PROJECT_OPTION_KEY , unchanged_options )
0 commit comments