4
4
import contextlib
5
5
import logging
6
6
import re
7
+ from collections import defaultdict
7
8
from collections .abc import Mapping , Sequence
8
9
from datetime import datetime , timedelta
9
10
from typing import Any
15
16
from sentry import release_health , tsdb
16
17
from sentry .eventstore .models import GroupEvent
17
18
from sentry .issues .constants import get_issue_tsdb_group_model , get_issue_tsdb_user_group_model
19
+ from sentry .issues .grouptype import GroupCategory
20
+ from sentry .models .group import Group
18
21
from sentry .receivers .rules import DEFAULT_RULE_LABEL , DEFAULT_RULE_LABEL_NEW
19
22
from sentry .rules import EventState
20
23
from sentry .rules .conditions .base import EventCondition
24
+ from sentry .tsdb .base import TSDBModel
21
25
from sentry .types .condition_activity import (
22
26
FREQUENCY_CONDITION_BUCKET_SIZE ,
23
27
ConditionActivity ,
24
28
round_to_five_minute ,
25
29
)
26
30
from sentry .utils import metrics
31
+ from sentry .utils .iterators import chunked
27
32
from sentry .utils .snuba import options_override
28
33
29
34
standard_intervals = {
49
54
COMPARISON_TYPE_COUNT : COMPARISON_TYPE_COUNT ,
50
55
COMPARISON_TYPE_PERCENT : COMPARISON_TYPE_PERCENT ,
51
56
}
57
+ SNUBA_LIMIT = 10000
52
58
53
59
54
60
class EventFrequencyForm (forms .Form ):
@@ -241,6 +247,29 @@ def get_rate(self, event: GroupEvent, interval: str, environment_id: str) -> int
241
247
242
248
return result
243
249
250
+ def get_sums (
251
+ self ,
252
+ keys : list [int ],
253
+ group : Group ,
254
+ model : TSDBModel ,
255
+ start : datetime ,
256
+ end : datetime ,
257
+ environment_id : str ,
258
+ referrer_suffix : str ,
259
+ ) -> Mapping [int , int ]:
260
+ sums : Mapping [int , int ] = self .tsdb .get_sums (
261
+ model = model ,
262
+ keys = keys ,
263
+ start = start ,
264
+ end = end ,
265
+ environment_id = environment_id ,
266
+ use_cache = True ,
267
+ jitter_value = group .id ,
268
+ tenant_ids = {"organization_id" : group .project .organization_id },
269
+ referrer_suffix = referrer_suffix ,
270
+ )
271
+ return sums
272
+
244
273
@property
245
274
def is_guessed_to_be_created_on_project_creation (self ) -> bool :
246
275
"""
@@ -265,19 +294,70 @@ class EventFrequencyCondition(BaseEventFrequencyCondition):
265
294
def query_hook (
266
295
self , event : GroupEvent , start : datetime , end : datetime , environment_id : str
267
296
) -> int :
268
- sums : Mapping [int , int ] = self .tsdb .get_sums (
269
- model = get_issue_tsdb_group_model (event .group .issue_category ),
297
+ sums : Mapping [int , int ] = self .get_sums (
270
298
keys = [event .group_id ],
299
+ group = event .group ,
300
+ model = get_issue_tsdb_group_model (event .group .issue_category ),
271
301
start = start ,
272
302
end = end ,
273
303
environment_id = environment_id ,
274
- use_cache = True ,
275
- jitter_value = event .group_id ,
276
- tenant_ids = {"organization_id" : event .group .project .organization_id },
277
304
referrer_suffix = "alert_event_frequency" ,
278
305
)
279
306
return sums [event .group_id ]
280
307
308
+ def get_chunked_sums (
309
+ self ,
310
+ groups : list [Group ],
311
+ start : datetime ,
312
+ end : datetime ,
313
+ environment_id : str ,
314
+ referrer_suffix : str ,
315
+ ) -> dict [int , int ]:
316
+ batch_sums : dict [int , int ] = defaultdict (int )
317
+ for group_chunk in chunked (groups , SNUBA_LIMIT ):
318
+ group = groups [0 ]
319
+ sums = self .get_sums (
320
+ keys = [group .id for group in groups ],
321
+ group = group ,
322
+ model = get_issue_tsdb_group_model (group .issue_category ),
323
+ start = start ,
324
+ end = end ,
325
+ environment_id = environment_id ,
326
+ referrer_suffix = referrer_suffix ,
327
+ )
328
+ batch_sums .update (sums )
329
+ return batch_sums
330
+
331
+ def batch_query_hook (
332
+ self , group_ids : Sequence [int ], start : datetime , end : datetime , environment_id : str
333
+ ) -> dict [int , int ]:
334
+ batch_sums : dict [int , int ] = defaultdict (int )
335
+ groups = Group .objects .filter (id__in = group_ids )
336
+ error_issues = [group for group in groups if group .issue_category == GroupCategory .ERROR ]
337
+ generic_issues = [group for group in groups if group .issue_category != GroupCategory .ERROR ]
338
+
339
+ if error_issues :
340
+ error_sums = self .get_chunked_sums (
341
+ groups = error_issues ,
342
+ start = start ,
343
+ end = end ,
344
+ environment_id = environment_id ,
345
+ referrer_suffix = "alert_event_frequency" ,
346
+ )
347
+ batch_sums .update (error_sums )
348
+
349
+ if generic_issues :
350
+ generic_sums = self .get_chunked_sums (
351
+ groups = generic_issues ,
352
+ start = start ,
353
+ end = end ,
354
+ environment_id = environment_id ,
355
+ referrer_suffix = "alert_event_frequency" ,
356
+ )
357
+ batch_sums .update (generic_sums )
358
+
359
+ return batch_sums
360
+
281
361
def get_preview_aggregate (self ) -> tuple [str , str ]:
282
362
return "count" , "roundedTime"
283
363
@@ -397,16 +477,13 @@ def query_hook(
397
477
percent_intervals [self .get_option ("interval" )][1 ].total_seconds () // 60
398
478
)
399
479
avg_sessions_in_interval = session_count_last_hour / (60 / interval_in_minutes )
400
-
401
- issue_count = self .tsdb .get_sums (
402
- model = get_issue_tsdb_group_model (event .group .issue_category ),
480
+ issue_count = self .get_sums (
403
481
keys = [event .group_id ],
482
+ group = event .group ,
483
+ model = get_issue_tsdb_group_model (event .group .issue_category ),
404
484
start = start ,
405
485
end = end ,
406
486
environment_id = environment_id ,
407
- use_cache = True ,
408
- jitter_value = event .group_id ,
409
- tenant_ids = {"organization_id" : event .group .project .organization_id },
410
487
referrer_suffix = "alert_event_frequency_percent" ,
411
488
)[event .group_id ]
412
489
if issue_count > avg_sessions_in_interval :
0 commit comments