Skip to content

feat(aci milestone 3): anomaly detection condition handler #88647

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

Open
wants to merge 19 commits into
base: master
Choose a base branch
from

Conversation

mifu67
Copy link
Contributor

@mifu67 mifu67 commented Apr 2, 2025

Create a condition handler for Anomaly Detection detector triggers. This data condition takes in a data packet and passes if the Seer evaluation corresponds to its condition result.

@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Apr 2, 2025
Copy link

codecov bot commented Apr 2, 2025

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
25964 1 25963 207
View the top 1 failed test(s) by shortest run time
tests.sentry.incidents.handlers.condition.test_anomaly_detection_handler.TestAnomalyDetectionHandler::test_passes_medium
Stack Traces | 3.95s run time
#x1B[1m#x1B[.../handlers/condition/test_anomaly_detection_handler.py#x1B[0m:103: in test_passes_medium
    assert self.dc.evaluate_value(self.data_packet) == DetectorPriorityLevel.OK
#x1B[1m#x1B[31mE   AssertionError: assert None == <DetectorPriorityLevel.OK: 0>#x1B[0m
#x1B[1m#x1B[31mE    +  where None = <bound method DataCondition.evaluate_value of <DataCondition at 0x7f48d1be56d0: id=12, type=<Condition.ANOMALY_DETECTI...tection'>, condition=None, condition_group=<DataConditionGroup at 0x7f48d1da70b0: id=6, logic_type=<Type.ANY: 'any'>>>>(DataPacket(source_id='24', packet={'subscription_id': '24', 'values': {'value': 1}, 'timestamp': datetime.datetime(2025, 5, 23, 0, 26, 9, 96998, tzinfo=datetime.timezone.utc), 'entity': 'test-entity'}))#x1B[0m
#x1B[1m#x1B[31mE    +    where <bound method DataCondition.evaluate_value of <DataCondition at 0x7f48d1be56d0: id=12, type=<Condition.ANOMALY_DETECTI...tection'>, condition=None, condition_group=<DataConditionGroup at 0x7f48d1da70b0: id=6, logic_type=<Type.ANY: 'any'>>>> = <DataCondition at 0x7f48d1be56d0: id=12, type=<Condition.ANOMALY_DETECTION: 'anomaly_detection'>, condition=None, condition_group=<DataConditionGroup at 0x7f48d1da70b0: id=6, logic_type=<Type.ANY: 'any'>>>.evaluate_value#x1B[0m
#x1B[1m#x1B[31mE    +      where <DataCondition at 0x7f48d1be56d0: id=12, type=<Condition.ANOMALY_DETECTION: 'anomaly_detection'>, condition=None, condition_group=<DataConditionGroup at 0x7f48d1da70b0: id=6, logic_type=<Type.ANY: 'any'>>> = <test_anomaly_detection_handler.TestAnomalyDetectionHandler testMethod=test_passes_medium>.dc#x1B[0m
#x1B[1m#x1B[31mE    +    and   DataPacket(source_id='24', packet={'subscription_id': '24', 'values': {'value': 1}, 'timestamp': datetime.datetime(2025, 5, 23, 0, 26, 9, 96998, tzinfo=datetime.timezone.utc), 'entity': 'test-entity'}) = <test_anomaly_detection_handler.TestAnomalyDetectionHandler testMethod=test_passes_medium>.data_packet#x1B[0m
#x1B[1m#x1B[31mE    +  and   <DetectorPriorityLevel.OK: 0> = DetectorPriorityLevel.OK#x1B[0m

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moved this file to the incidents directory.

@@ -5,6 +5,7 @@
from typing import Any

from sentry import features
from sentry.incidents.handlers.condition import * # noqa
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Imported so that the condition handler gets added to the registry. Maybe there's a better solution?



# placeholder until we create this in the workflow engine model
class DetectorError(Exception):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Calling this out as a TODO (need to add it to the workflow engine + build exception handling for it within process_detectors)

Copy link
Member

Choose a reason for hiding this comment

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

Maybe this could live in either sentry/workflow_engine/processors/detector.py or a new utils file in the same dir?

SEER_ANOMALY_DETECTION_ENDPOINT_URL,
json.dumps(detect_anomalies_request).encode("utf-8"),
)
except (TimeoutError, MaxRetryError):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Everything below this line is error handling (copied from the legacy method)

@mifu67 mifu67 requested a review from ceorourke May 20, 2025 23:05
@mifu67 mifu67 marked this pull request as ready for review May 20, 2025 23:05
@mifu67 mifu67 requested review from a team as code owners May 20, 2025 23:05
# covers both None and []
if not anomaly_data:
# something went wrong during evaluation
raise DetectorError("Error during Seer data evaluation process.")
Copy link
Member

Choose a reason for hiding this comment

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

should this return False instead of raising an exception, if you're going to build exception handling for it? what's the outcome of the exception handling?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We want this to raise an error instead of returning False, because returning False indicates that we should set the detector priority level to OK.

I think we actually want to change this condition handler to emit multiple detector priority levels according to the anomaly detection result 🤔

threshold_type = comparison["threshold_type"]
confidence = comparison["confidence"]

subscription: QuerySubscription = QuerySubscription.objects.get(id=update.source_id)
Copy link
Member

Choose a reason for hiding this comment

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

should we have handling around this in case it doesn't exist?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's let it fail loudly—I don't think this should happen.



# placeholder until we create this in the workflow engine model
class DetectorError(Exception):
Copy link
Member

Choose a reason for hiding this comment

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

Maybe this could live in either sentry/workflow_engine/processors/detector.py or a new utils file in the same dir?

Comment on lines 61 to 64
sensitivity = comparison["sensitivity"]
seasonality = comparison["seasonality"]
threshold_type = comparison["threshold_type"]
confidence = comparison["confidence"]
Copy link
Member

Choose a reason for hiding this comment

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

What else is in comparison? I'm wondering if we could just pass that var to get_anomaly_data_from_seer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, that's a good idea—I'd be open to it.

},
)
return None
return ts
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 add tests for this method? Probably can mostly copy/paste from the existing ones, just don't wanna lose the coverage when we delete the old stuff.

@mifu67 mifu67 requested a review from a team as a code owner May 22, 2025 21:47
"seasonality": AnomalyDetectionSeasonality.AUTO,
"threshold_type": AnomalyDetectionThresholdType.ABOVE_AND_BELOW,
},
condition_result=DetectorPriorityLevel.HIGH,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@saponifi3d calling out that we want this data condition to emit whatever the result of evaluate_value() is, not a static condition result.

@getsentry getsentry deleted a comment from github-actions bot May 22, 2025
@mifu67 mifu67 removed the request for review from a team May 22, 2025 22:00
) -> list[TimeSeriesPoint] | None:
snuba_query: SnubaQuery = subscription.snuba_query
aggregation_value = subscription_update["values"].get("value")
source_id = subscription.id
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because we're passing in the query subscription, we don't need to pass source_id and source_type.

@mifu67 mifu67 requested a review from ceorourke May 22, 2025 22:18
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.

4 participants