1
1
from collections import defaultdict
2
2
from datetime import datetime , timedelta
3
3
from enum import Enum
4
- from typing import Any , Dict , Mapping , Optional , Set , Tuple
4
+ from typing import Any , Dict , List , Mapping , Optional , Set , Tuple
5
5
6
6
import pytz
7
7
import sentry_sdk
10
10
from sentry .dynamic_sampling import get_redis_client_for_ds
11
11
from sentry .incidents .models import AlertRule
12
12
from sentry .models import DashboardWidgetQuery , Organization , Project
13
+ from sentry .snuba import metrics_performance
13
14
from sentry .snuba .discover import query as discover_query
14
15
from sentry .snuba .metrics_enhanced_performance import query as performance_query
15
16
from sentry .tasks .base import instrumented_task
16
17
from sentry .utils import json
17
18
19
+ # The time range over which the check script queries the data for determining the compatibility state.
20
+ QUERY_TIME_RANGE_IN_DAYS = 1
21
+
18
22
# List of minimum SDK versions that support Performance at Scale.
19
23
# The list is defined here:
20
24
# https://docs.sentry.io/product/performance/performance-at-scale/getting-started
155
159
# Match multiple tags that contain this.
156
160
"stack." ,
157
161
"error." ,
162
+ # Match generic values.
163
+ "issue" ,
164
+ "exception" ,
158
165
]
159
166
160
167
@@ -204,9 +211,14 @@ def compare_versions(cls, version1, version2):
204
211
205
212
@classmethod
206
213
def format_results (
207
- cls , organization , unsupported_widgets , unsupported_alerts , outdated_sdks_per_project
214
+ cls ,
215
+ organization ,
216
+ projects_compatibility ,
217
+ unsupported_widgets ,
218
+ unsupported_alerts ,
219
+ outdated_sdks_per_project ,
208
220
):
209
- results : Dict [str , Any ] = {}
221
+ results : Dict [str , Any ] = {"projects_compatibility" : projects_compatibility }
210
222
211
223
widgets = []
212
224
for dashboard_id , unsupported_widgets in unsupported_widgets .items ():
@@ -318,7 +330,7 @@ def get_sdks_version_used(cls, organization_id, project_objects):
318
330
params = {
319
331
"organization_id" : organization_id ,
320
332
"project_objects" : project_objects ,
321
- "start" : datetime .now (tz = pytz .UTC ) - timedelta (days = 1 ),
333
+ "start" : datetime .now (tz = pytz .UTC ) - timedelta (days = QUERY_TIME_RANGE_IN_DAYS ),
322
334
"end" : datetime .now (tz = pytz .UTC ),
323
335
}
324
336
@@ -343,7 +355,7 @@ def is_metrics_data(cls, organization_id, project_objects, query):
343
355
params = {
344
356
"organization_id" : organization_id ,
345
357
"project_objects" : project_objects ,
346
- "start" : datetime .now (tz = pytz .UTC ) - timedelta (days = 1 ),
358
+ "start" : datetime .now (tz = pytz .UTC ) - timedelta (days = QUERY_TIME_RANGE_IN_DAYS ),
347
359
"end" : datetime .now (tz = pytz .UTC ),
348
360
}
349
361
@@ -367,6 +379,7 @@ def get_excluded_conditions(cls):
367
379
for condition in EXCLUDED_CONDITIONS :
368
380
# We want to build an AND condition with multiple negated elements.
369
381
qs &= ~ Q (conditions__icontains = condition )
382
+ qs &= ~ Q (fields__icontains = condition )
370
383
371
384
return qs
372
385
@@ -395,12 +408,56 @@ def get_all_alerts_of_organization(cls, organization_id):
395
408
.values_list ("id" , "snuba_query__aggregate" , "snuba_query__query" )
396
409
)
397
410
411
+ @classmethod
412
+ def get_organization_metrics_compatibility (cls , organization , project_objects ):
413
+ data : Dict [str , List [Any ]] = {
414
+ "incompatible_projects" : [],
415
+ "compatible_projects" : [],
416
+ }
417
+
418
+ params = {
419
+ "organization_id" : organization .id ,
420
+ "project_objects" : project_objects ,
421
+ "start" : datetime .now (tz = pytz .UTC ) - timedelta (days = QUERY_TIME_RANGE_IN_DAYS ),
422
+ "end" : datetime .now (tz = pytz .UTC ),
423
+ }
424
+
425
+ project_ids = [project .id for project in project_objects ]
426
+
427
+ count_has_txn = "count_has_transaction_name()"
428
+ count_null = "count_null_transactions()"
429
+ compatible_results = metrics_performance .query (
430
+ selected_columns = [
431
+ "project.id" ,
432
+ count_null ,
433
+ count_has_txn ,
434
+ ],
435
+ params = params ,
436
+ query = f"{ count_null } :0 AND { count_has_txn } :>0" ,
437
+ referrer = "api.organization-events" ,
438
+ functions_acl = ["count_null_transactions" , "count_has_transaction_name" ],
439
+ use_aggregate_conditions = True ,
440
+ )
441
+
442
+ data ["compatible_projects" ] = sorted (
443
+ row ["project.id" ] for row in compatible_results ["data" ]
444
+ )
445
+ data ["incompatible_projects" ] = sorted (
446
+ list (set (project_ids ) - set (data ["compatible_projects" ]))
447
+ )
448
+
449
+ return data
450
+
398
451
@classmethod
399
452
def run_compatibility_check (cls , org_id ):
400
453
organization = Organization .objects .get (id = org_id )
401
454
402
455
all_projects = list (Project .objects .using_replica ().filter (organization = organization ))
403
456
457
+ projects_compatibility = cls .get_organization_metrics_compatibility (
458
+ organization , all_projects
459
+ )
460
+
404
461
unsupported_widgets = defaultdict (list )
405
462
for (
406
463
widget_id ,
@@ -455,7 +512,11 @@ def run_compatibility_check(cls, org_id):
455
512
outdated_sdks_per_project = {}
456
513
457
514
return cls .format_results (
458
- organization , unsupported_widgets , unsupported_alerts , outdated_sdks_per_project
515
+ organization ,
516
+ projects_compatibility ,
517
+ unsupported_widgets ,
518
+ unsupported_alerts ,
519
+ outdated_sdks_per_project ,
459
520
)
460
521
461
522
0 commit comments