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(aci): event frequency condition handler #82551

Merged
merged 11 commits into from
Jan 15, 2025

Conversation

cathteng
Copy link
Member

@cathteng cathteng commented Dec 23, 2024

Add base event frequency condition handler, event frequency count and event frequency percent condition handler.

Each existing condition class technically handles two distinct condition types: getting the count for an interval, and comparing percent increase of the count for an interval with the same interval X time in the past.

The base condition handler borrows elements from BaseEventFrequencyCondition related to making the bulk Snuba query -- it might not yet contain everything necessary for delayed processing, but is enough for tests.

Copy link

codecov bot commented Dec 24, 2024

Codecov Report

Attention: Patch coverage is 92.15686% with 16 lines in your changes missing coverage. Please review.

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...handlers/condition/event_frequency_base_handler.py 80.64% 12 Missing ⚠️
...ine/handlers/condition/event_frequency_handlers.py 92.68% 3 Missing ⚠️
...rc/sentry/workflow_engine/models/data_condition.py 90.90% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #82551      +/-   ##
==========================================
- Coverage   87.52%   87.49%   -0.03%     
==========================================
  Files        9488     9411      -77     
  Lines      537867   537366     -501     
  Branches    21174    21174              
==========================================
- Hits       470750   470179     -571     
- Misses      66760    66830      +70     
  Partials      357      357              

Comment on lines 208 to 58
def batch_query(
self, group_ids: set[int], start: datetime, end: datetime, environment_id: int
) -> dict[int, int]:
batch_sums: dict[int, int] = defaultdict(int)
groups = Group.objects.filter(id__in=group_ids).values(
"id", "type", "project_id", "project__organization_id"
)
error_issue_ids, generic_issue_ids = self.get_error_and_generic_group_ids(groups)
organization_id = self.get_value_from_groups(groups, "project__organization_id")

if error_issue_ids and organization_id:
error_sums = self.get_chunked_result(
tsdb_function=tsdb.backend.get_sums,
model=get_issue_tsdb_group_model(GroupCategory.ERROR),
group_ids=error_issue_ids,
organization_id=organization_id,
start=start,
end=end,
environment_id=environment_id,
referrer_suffix="batch_alert_event_frequency",
)
batch_sums.update(error_sums)

if generic_issue_ids and organization_id:
generic_sums = self.get_chunked_result(
tsdb_function=tsdb.backend.get_sums,
# this isn't necessarily performance, just any non-error category
model=get_issue_tsdb_group_model(GroupCategory.PERFORMANCE),
group_ids=generic_issue_ids,
organization_id=organization_id,
start=start,
end=end,
environment_id=environment_id,
referrer_suffix="batch_alert_event_frequency",
)
batch_sums.update(generic_sums)

return batch_sums
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copied from

class EventFrequencyCondition(BaseEventFrequencyCondition):

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 - i wonder if we should have these defined with the DataSources? I'm trying to think about how this module will grow and change over time.

I think it makes sense to have it here, but as i think about what would we need to change if we do Snuba Query -> EAP, we'd have to make a lot of changes to the workflow engine to support that here. If we had this setup by type, we could move the code into the area and hook in here with a registry.

I'm not sold on ^ as a design, but i'm a little worried about how many condition handlers we'll have here, and how tightly coupled they'll be to the workflow engine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discussed offline, we should probably discuss how these conditions will look for the different product verticals, especially for those where a single occurrence != there was an issue at X time (crons, metric issues, uptime) 🤔

although i will say it does seem like all kinds of issues go through this logic today.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cathteng cathteng marked this pull request as ready for review December 26, 2024 19:55
@cathteng cathteng requested a review from a team December 26, 2024 19:55
assert dc.condition_group == dcg


class EventFrequencyQueryTest(EventFrequencyQueryTestBase):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the same as

class EventFrequencyQueryTest(EventFrequencyQueryTestBase):

@cathteng cathteng marked this pull request as draft January 2, 2025 20:00
@cathteng cathteng force-pushed the cathy/aci/event-frequency-handler branch from 888db48 to eb54786 Compare January 3, 2025 19:45
@cathteng cathteng marked this pull request as ready for review January 3, 2025 19:45
@cathteng cathteng requested a review from ceorourke January 6, 2025 17:24
src/sentry/workflow_engine/models/data_condition.py Outdated Show resolved Hide resolved
src/sentry/workflow_engine/models/data_condition.py Outdated Show resolved Hide resolved
Comment on lines 208 to 58
def batch_query(
self, group_ids: set[int], start: datetime, end: datetime, environment_id: int
) -> dict[int, int]:
batch_sums: dict[int, int] = defaultdict(int)
groups = Group.objects.filter(id__in=group_ids).values(
"id", "type", "project_id", "project__organization_id"
)
error_issue_ids, generic_issue_ids = self.get_error_and_generic_group_ids(groups)
organization_id = self.get_value_from_groups(groups, "project__organization_id")

if error_issue_ids and organization_id:
error_sums = self.get_chunked_result(
tsdb_function=tsdb.backend.get_sums,
model=get_issue_tsdb_group_model(GroupCategory.ERROR),
group_ids=error_issue_ids,
organization_id=organization_id,
start=start,
end=end,
environment_id=environment_id,
referrer_suffix="batch_alert_event_frequency",
)
batch_sums.update(error_sums)

if generic_issue_ids and organization_id:
generic_sums = self.get_chunked_result(
tsdb_function=tsdb.backend.get_sums,
# this isn't necessarily performance, just any non-error category
model=get_issue_tsdb_group_model(GroupCategory.PERFORMANCE),
group_ids=generic_issue_ids,
organization_id=organization_id,
start=start,
end=end,
environment_id=environment_id,
referrer_suffix="batch_alert_event_frequency",
)
batch_sums.update(generic_sums)

return batch_sums
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 - i wonder if we should have these defined with the DataSources? I'm trying to think about how this module will grow and change over time.

I think it makes sense to have it here, but as i think about what would we need to change if we do Snuba Query -> EAP, we'd have to make a lot of changes to the workflow engine to support that here. If we had this setup by type, we could move the code into the area and hook in here with a registry.

I'm not sold on ^ as a design, but i'm a little worried about how many condition handlers we'll have here, and how tightly coupled they'll be to the workflow engine.

Comment on lines 35 to 42
EVENT_FREQUENCY = "event_frequency"
EVENT_UNIQUE_USER_FREQUENCY = "event_unique_user_frequency"
EVENT_FREQUENCY_PERCENT = "event_frequency_percent"
EVENT_UNIQUE_USER_FREQUENCY_WITH_CONDITIONS = "event_unique_user_frequency_with_conditions"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 do we need different condition handlers for each of these to access the specific data to compare against? Are there more to come for event frequency? How would we know which one of these conditions to use in the UI from a single UI component?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2025-01-08 at 14 50 05

these are what the frequency conditions can look like in the UI today... today we do some funky stuff to get it to look like this
https://github.com/getsentry/sentry/blob/0d73e5379eca69ae2da588836732ef1add3aee91/static/app/views/alerts/utils/constants.tsx#L1-L35

but there are actually only these 4 conditions/handlers for event frequency conditions

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after looking at this more i realized each condition class basically handles 2 conditions: event count and percent frequency. i added accounting for this in a single condition class because we are able to share some queries in the percent case with event count in delayed processing (percent has 2 queries, 1 of which can overlap with even count)

src/sentry/workflow_engine/models/data_condition.py Outdated Show resolved Hide resolved
@cathteng cathteng requested review from a team, saponifi3d and ceorourke January 9, 2025 23:44
):
@staticmethod
def evaluate_value(value: list[int], comparison: Any) -> DataConditionResult:
comparison_type = comparison["comparison_type"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you type comparison as a dict?

Copy link
Contributor

@saponifi3d saponifi3d Jan 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 we should probably add a json schema to the comparison field? it'd be nice if we could do this in the base frequency class to ensure these new conditions are correct.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because this is so generic, comparison can be anything that reasonably splats into models.JSONField...

@cathteng cathteng force-pushed the cathy/aci/event-frequency-handler branch from 990504d to 8d75328 Compare January 13, 2025 21:14
Comment on lines 57 to 95
@condition_handler_registry.register(Condition.EVENT_FREQUENCY_COUNT)
class EventFrequencyCountHandler(EventFrequencyConditionHandler, DataConditionHandler[int]):
@staticmethod
def evaluate_value(value: int, comparison: Any) -> DataConditionResult:
return value > comparison["value"]


@condition_handler_registry.register(Condition.EVENT_FREQUENCY_PERCENT)
class EventFrequencyPercentHandler(EventFrequencyConditionHandler, DataConditionHandler[list[int]]):
@staticmethod
def evaluate_value(value: list[int], comparison: Any) -> DataConditionResult:
if len(value) != 2:
return False
return percent_increase(value[0], value[1]) > comparison["value"]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the main part

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@saponifi3d i think i need to add some validation on all DataCondition.comparison fields, will think about it in an upcoming PR

Copy link
Contributor

@saponifi3d saponifi3d left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall, lgtm, it'd be nice if we address the comparison schema comment before merging though.

):
@staticmethod
def evaluate_value(value: list[int], comparison: Any) -> DataConditionResult:
comparison_type = comparison["comparison_type"]
Copy link
Contributor

@saponifi3d saponifi3d Jan 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 we should probably add a json schema to the comparison field? it'd be nice if we could do this in the base frequency class to ensure these new conditions are correct.

from sentry.workflow_engine.types import DataConditionHandler, DataConditionResult


class EventFrequencyConditionHandler(BaseEventFrequencyConditionHandler):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: if we're going to have all these classes in 1 file, might as well include the base here too (otherwise, i'd split each of these classes as a separate file, but that's a bit JS-y too)

Copy link
Member Author

@cathteng cathteng Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are 4 existing condition classes that are related but represent 2 conditions (count and percent change), and i am grouping together these 2 very similar conditions

Comment on lines +36 to +38
self.assert_slow_cond_passes(dc, 1001)
self.assert_slow_cond_does_not_pass(dc, 999)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.assert_slow_cond_passes(dc, 1001)
self.assert_slow_cond_does_not_pass(dc, 999)
self.assert_slow_cond_passes(dc, [1001])
self.assert_slow_cond_does_not_pass(dc, [999])

^ should fix the mypy error.

@cathteng cathteng force-pushed the cathy/aci/event-frequency-handler branch from ffa1ca5 to e7504cf Compare January 15, 2025 21:41
@cathteng cathteng enabled auto-merge (squash) January 15, 2025 21:42
@cathteng cathteng merged commit 8cff0dc into master Jan 15, 2025
48 checks passed
@cathteng cathteng deleted the cathy/aci/event-frequency-handler branch January 15, 2025 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Scope: Backend Automatically applied to PRs that change backend components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants