src/sentry/issue_detection/README.md
Performance issues are composed of a few different parts which control how they manifest in the app to users. This document will outline those parts, and then explain how to go about creating a new performance detector, and experimenting with changes to performance detectors.
Performance Issues are built on top of the Issue Platform which already has great documentation as to its usage. The internal logic for each detector varies, but here's a general walkthrough of how a performance issue is generated end-to-end.
save() method.
event_type is a transaction, it also calls save_transaction_events()_detect_performance_problems and _send_occurrence_to_platform functions._detect_performance_problem function takes in the transaction event, and spits out a list of PerformanceProblems which are generated from running the detector on all of the spans in the event.
DETECTOR_CLASSESPerformanceDetector.is_detection_allowed_for_system() from base.py
DETECTOR_TYPE_ISSUE_CREATION_TO_SYSTEM_OPTION from base.pyPerformanceDetector.type isn't in that dictionary, it will not run the detector.True.x < 1.0), it will run the detector 100 * x% of the time.run_detector_on_data() in performance_detection.pyPerformanceProblems if it returns False:
PerformanceDetector.is_creation_allowed() - Usually checking project's detector settingsPerformanceProblems from _detect_performance_problems on job["performance_problems"]_send_occurrence_to_platform which reads job["performance_problems"]
PerformanceProblem into an IssueOccurrenceproduce_occurrence_to_kafka from producer.py which passes the occurrence along to the Issue PlatformFor context, the Issue Platform operates off of the the GroupType subclasses (from grouptype.py). The issue platform docs provide a way to control the rollout of new GroupTypes via the released property. Keep in mind, this rollout is entirely separate from the PerformanceDetector! The checks within _detect_performance_problem may skip running the detector, or skip creating problems completely all before they ever reach the Issue Platform.
The current performance detectors make some assumptions about the spans that are passed along to visit_span() when they are traversing the transaction event and scanning for problems. It's possible some detectors may assemble their own data structure internally, but generally performance detectors assume:
There are quite a few places which need to be updated when adding a new performance detector:
PerformanceDetector subclass (see base.py) to the new fileDETECTOR_CLASSES in performance_detection.pyDetectorType in base.pyperformance.issues.<detector_name>.problem-creation option in defaults.pyDETECTOR_TYPE_ISSUE_CREATION_TO_SYSTEM_OPTION in base.py with that new optionGroupType in grouptype.pyget_detection_settings() (in performance_detection.py)
DetectorType, with a value of an empty dictionarysettings[key_name]
InternalProjectOptions or ConfigurableThresholdsproject_settings_to_group_map/thresholds_to_manage_map using the new GroupTypeProjectPerformanceIssueSettingsSerializer to allow it to be validated from the inbound API.projectPerformance.tsx) should add the new field.get_merged_settings() in performance_detection.pyPerformanceDetector subclass
type and settings_key attributes with the new DetectorTypeis_event_eligible() to allow early exits.is_creation_allowed() to check a creation flag.visit_span() and on_complete(), adding any identified PerformanceProblems to self.stored_problems as you go.Since performance detectors have such high throughput (by operating on all ingested spans), changes to things like logical flow, or fingerprinting can have huge unintended consequences. We do the best we can to write tests, but it's not realistic to test every possible edge case that we'll see in production. Instead we have to be strategic about making these sorts of edits.
Currently, we have a loose system called experiments, which can be found in the /detectors/experiments and tests/.../issue_detection/experiments directories. Experiments are useful for when we want to make a risky change to a live detector, but want to double check the output before releasing it. You can run a new experiment by following these steps:
GroupType in grouptype.py. By convention, the type_id uses 19xx (e.g. 1002 -> 1902), and the slug and description include 'experimental'. Ensure released is set to FalseDetectorType in base.py.get_detection_settings() (in performance_detection.py) with the new experimental DetectorType key. The value can stay as a copy of the existing detector.ExperimentalDetectorType and GroupType to the experimental onesis_detection_allowed_for_system, otherwise you'll have to meter the detector rollout with an entry to DETECTOR_TYPE_ISSUE_CREATION_TO_SYSTEM_OPTION in base.pyGroupType for fingerprinting, but if not, make a temporary change manually. If you don't do this, then once you start ingesting production data, the groups will be identical to what they would be if you GA'd, but without running post_process. This will mean once GA'd, the issue platform sees that the groups already exist and so won't fire notifications, trigger alerts, assign correctly, or any of the other steps from post_process.PerformanceDetector to DETECTOR_CLASSES in performance_detection.pyDetectorType and GroupType to the experimental ones@exclude_experimental_detectors() decorator for a quick edit.Once all the above is merged, we can begin functional changes. You can try out new detection schemes, strategies or fingerprinting, without any impact on user experience. You'll also be able to add tests, and iterate on the changes as much as you want without affecting the existing detector.
One note though, you may want to keep an eye on the metrics for run_detector_on_data.<detector-name> to ensure it doesn't unreasonably increase the runtime compared to the existing detector.
To start ingesting user data, you can use the issue platform release flags:
sentry createissueflag --slug=<experiment-grouptype-slug> --owner=<your-team>
These flags don't need to be defined in sentry, you just have to add them to Flagpole.
You'll only want to GA the 'ingest' flag, but the 'post-process' and 'ui' flags can help you get an end to end experience prior to adding your changes to the existing detector.
Once the change has been deemed sufficiently tested, reverse the setup:
Experimental from the detector class nameDetectorType and GroupType to the existing onesis_detection_allowed_for_system if applicable.DetectorType and GroupType to the existing onesOnce all that is good to go, ensure you clean up the experiment files and any options/flags you may have set while iterating.