-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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): enqueue workflows for delayed processing #83548
Changes from 10 commits
c3ec931
9f8ea09
8630b00
30739b2
0ffd148
6a51006
be626eb
4b1708d
44035ae
6bbdb9a
84ee9ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,7 @@ | |
) | ||
from sentry.workflow_engine.models.data_condition import Condition | ||
from sentry.workflow_engine.registry import condition_handler_registry | ||
from sentry.workflow_engine.types import DataConditionHandler, DataConditionResult | ||
from sentry.workflow_engine.types import DataConditionHandler, DataConditionResult, WorkflowJob | ||
|
||
|
||
class EventFrequencyConditionHandler(BaseEventFrequencyConditionHandler): | ||
|
@@ -59,7 +59,7 @@ def get_result(model: TSDBModel, group_ids: list[int]) -> dict[int, int]: | |
|
||
|
||
@condition_handler_registry.register(Condition.EVENT_FREQUENCY_COUNT) | ||
class EventFrequencyCountHandler(EventFrequencyConditionHandler, DataConditionHandler[int]): | ||
class EventFrequencyCountHandler(EventFrequencyConditionHandler, DataConditionHandler[WorkflowJob]): | ||
comparison_json_schema = { | ||
"type": "object", | ||
"properties": { | ||
|
@@ -71,12 +71,16 @@ class EventFrequencyCountHandler(EventFrequencyConditionHandler, DataConditionHa | |
} | ||
|
||
@staticmethod | ||
def evaluate_value(value: int, comparison: Any) -> DataConditionResult: | ||
return value > comparison["value"] | ||
def evaluate_value(value: WorkflowJob, comparison: Any) -> DataConditionResult: | ||
if len(value.get("snuba_results", [])) != 1: | ||
return False | ||
return value["snuba_results"][0] > comparison["value"] | ||
|
||
|
||
@condition_handler_registry.register(Condition.EVENT_FREQUENCY_PERCENT) | ||
class EventFrequencyPercentHandler(EventFrequencyConditionHandler, DataConditionHandler[list[int]]): | ||
class EventFrequencyPercentHandler( | ||
EventFrequencyConditionHandler, DataConditionHandler[WorkflowJob] | ||
): | ||
comparison_json_schema = { | ||
"type": "object", | ||
"properties": { | ||
|
@@ -89,7 +93,10 @@ class EventFrequencyPercentHandler(EventFrequencyConditionHandler, DataCondition | |
} | ||
|
||
@staticmethod | ||
def evaluate_value(value: list[int], comparison: Any) -> DataConditionResult: | ||
if len(value) != 2: | ||
def evaluate_value(value: WorkflowJob, comparison: Any) -> DataConditionResult: | ||
if len(value.get("snuba_results", [])) != 2: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this a common scenario or a weird snuba blip? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's possible we don't have the snuba results when we are evaluating the triggers outside of delayed processing |
||
return False | ||
return percent_increase(value[0], value[1]) > comparison["value"] | ||
return ( | ||
percent_increase(value["snuba_results"][0], value["snuba_results"][1]) | ||
> comparison["value"] | ||
) |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -1,22 +1,82 @@ | ||||||||
import logging | ||||||||
from collections import defaultdict | ||||||||
|
||||||||
import sentry_sdk | ||||||||
|
||||||||
from sentry.utils import metrics | ||||||||
from sentry.workflow_engine.models import Detector, Workflow | ||||||||
from sentry import buffer | ||||||||
from sentry.utils import json, metrics | ||||||||
from sentry.workflow_engine.models import Detector, Workflow, WorkflowDataConditionGroup | ||||||||
from sentry.workflow_engine.models.workflow import get_slow_conditions | ||||||||
from sentry.workflow_engine.processors.action import evaluate_workflow_action_filters | ||||||||
from sentry.workflow_engine.processors.data_condition_group import evaluate_condition_group | ||||||||
from sentry.workflow_engine.processors.detector import get_detector_by_event | ||||||||
from sentry.workflow_engine.types import WorkflowJob | ||||||||
|
||||||||
logger = logging.getLogger(__name__) | ||||||||
|
||||||||
WORKFLOW_ENGINE_BUFFER_LIST_KEY = "workflow_engine_delayed_processing_buffer" | ||||||||
|
||||||||
|
||||||||
def get_data_condition_groups_to_fire( | ||||||||
workflows: set[Workflow], job: WorkflowJob | ||||||||
) -> dict[int, list[int]]: | ||||||||
workflow_action_groups: dict[int, list[int]] = defaultdict(list) | ||||||||
|
||||||||
workflow_ids = {workflow.id for workflow in workflows} | ||||||||
|
||||||||
workflow_dcgs = WorkflowDataConditionGroup.objects.filter( | ||||||||
workflow_id__in=workflow_ids | ||||||||
).select_related("condition_group", "workflow") | ||||||||
|
||||||||
for workflow_dcg in workflow_dcgs: | ||||||||
action_condition = workflow_dcg.condition_group | ||||||||
evaluation, result = evaluate_condition_group(action_condition, job) | ||||||||
|
||||||||
if evaluation: | ||||||||
workflow_action_groups[workflow_dcg.workflow_id].append(action_condition.id) | ||||||||
|
||||||||
return workflow_action_groups | ||||||||
|
||||||||
|
||||||||
def enqueue_workflows( | ||||||||
workflows: set[Workflow], | ||||||||
job: WorkflowJob, | ||||||||
) -> None: | ||||||||
event = job["event"] | ||||||||
project_id = event.group.project.id | ||||||||
workflow_action_groups = get_data_condition_groups_to_fire(workflows, job) | ||||||||
|
||||||||
for workflow in workflows: | ||||||||
buffer.backend.push_to_sorted_set(key=WORKFLOW_ENGINE_BUFFER_LIST_KEY, value=project_id) | ||||||||
|
||||||||
if_dcgs = workflow_action_groups.get(workflow.id, []) | ||||||||
if not if_dcgs: | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this reads a little strange - why is the var name There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these are IF data condition groups There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 maybe we call them |
||||||||
continue | ||||||||
|
||||||||
if_dcg_fields = ":".join(map(str, if_dcgs)) | ||||||||
|
||||||||
value = json.dumps({"event_id": event.event_id, "occurrence_id": event.occurrence_id}) | ||||||||
buffer.backend.push_to_hash( | ||||||||
model=Workflow, | ||||||||
filters={"project": project_id}, | ||||||||
field=f"{workflow.id}:{event.group.id}:{if_dcg_fields}", | ||||||||
value=value, | ||||||||
) | ||||||||
|
||||||||
|
||||||||
def evaluate_workflow_triggers(workflows: set[Workflow], job: WorkflowJob) -> set[Workflow]: | ||||||||
triggered_workflows: set[Workflow] = set() | ||||||||
workflows_to_enqueue: set[Workflow] = set() | ||||||||
|
||||||||
for workflow in workflows: | ||||||||
if workflow.evaluate_trigger_conditions(job): | ||||||||
triggered_workflows.add(workflow) | ||||||||
else: | ||||||||
if get_slow_conditions(workflow): | ||||||||
# enqueue to be evaluated later | ||||||||
workflows_to_enqueue.add(workflow) | ||||||||
|
||||||||
enqueue_workflows(workflows_to_enqueue, job) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: save the couple of cpu cycles and only enqueue if we have something to enqueue
Suggested change
|
||||||||
|
||||||||
return triggered_workflows | ||||||||
|
||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,6 +40,7 @@ class WorkflowJob(EventJob, total=False): | |
has_alert: bool | ||
has_escalated: bool | ||
workflow: Workflow | ||
snuba_results: list[int] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤔 is the value in this that we can re-use i think this is a bit of a smell that the abstraction might not be quite right in either delayed processing or the evaluate_workflow_triggers 🤔 mind adding a TODO here so i can come back and take a look? i'm not sure if this is the best approach, but seems okay for now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah the value is the we can use at least the |
||
|
||
|
||
class ActionHandler: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
update this to evaluate
WorkflowJob
so we can reuseevaluate_workflow_triggers
in delayed processing, and populatesnuba_results
insideWorkflowJob
after we make the snuba queries