diff --git a/src/sentry/options/defaults.py b/src/sentry/options/defaults.py index 4b6212b30400dc..72a4ee0f017569 100644 --- a/src/sentry/options/defaults.py +++ b/src/sentry/options/defaults.py @@ -1471,6 +1471,18 @@ flags=FLAG_AUTOMATOR_MODIFIABLE, ) +register( + "sentry-metrics.extrapolation.enable_transactions", + default=False, + flags=FLAG_AUTOMATOR_MODIFIABLE, +) + +register( + "sentry-metrics.extrapolation.enable_spans", + default=False, + flags=FLAG_AUTOMATOR_MODIFIABLE, +) + register( "sentry-metrics.extrapolation.duplication-limit", default=0, diff --git a/src/sentry/relay/config/metric_extraction.py b/src/sentry/relay/config/metric_extraction.py index 8b3f8d2c96e02d..d24bc031c7c7ad 100644 --- a/src/sentry/relay/config/metric_extraction.py +++ b/src/sentry/relay/config/metric_extraction.py @@ -149,10 +149,24 @@ def get_extrapolation_config(project: Project) -> MetricExtrapolationConfig | No # Extrapolation applies to extracted metrics. This enables extrapolation for # the entire `custom` namespace, but this does not extrapolate old custom # metrics sent from the SDK directly. - return { + config: MetricExtrapolationConfig = { "include": ["?:custom/*"], + "exclude": [], } + if options.get("sentry-metrics.extrapolation.enable_transactions"): + config["include"] += ["?:transactions/*"] + config["exclude"] += [ + "c:transactions/usage@none", # stats + "c:transactions/count_per_root_project@none", # dynamic sampling + ] + + if options.get("sentry-metrics.extrapolation.enable_spans"): + config["include"] += ["?:spans/*"] + config["exclude"] += ["c:spans/usage@none"] # stats + + return config + def get_on_demand_metric_specs( timeout: TimeChecker, project: Project diff --git a/tests/sentry/relay/config/test_metric_extraction.py b/tests/sentry/relay/config/test_metric_extraction.py index acb7dc2c4ffd50..a0901d215985c0 100644 --- a/tests/sentry/relay/config/test_metric_extraction.py +++ b/tests/sentry/relay/config/test_metric_extraction.py @@ -6,6 +6,7 @@ import pytest from django.utils import timezone +from sentry_relay.processing import normalize_project_config from sentry.incidents.models.alert_rule import AlertRule, AlertRuleProjects from sentry.models.dashboard_widget import DashboardWidgetQuery, DashboardWidgetQueryOnDemand @@ -2239,3 +2240,43 @@ def test_get_metric_extraction_config_span_attributes_above_max_limit( assert config assert len(config["metrics"]) == 1 + + +@django_db_all +@override_options( + { + "sentry-metrics.extrapolation.enable_transactions": True, + "sentry-metrics.extrapolation.enable_spans": True, + } +) +def test_get_metric_extrapolation_config(default_project: Project) -> None: + default_project.update_option("sentry:extrapolate_metrics", True) + + # Create a dummy extraction rule to ensure there is at least one + # metric. Otherwise, the spec will be empty. + attr_config = { + "spanAttribute": "span.duration", + "aggregates": ["count"], + "unit": "none", + "tags": [], + "conditions": [{"id": 1, "value": "bar:baz"}], + } + SpanAttributeExtractionRuleConfig.from_dict(attr_config, 1, default_project) + + with Feature( + ["organizations:metrics-extrapolation", "organizations:custom-metrics-extraction-rule"] + ): + config = get_metric_extraction_config(TimeChecker(timedelta(seconds=0)), default_project) + + assert config and config["extrapolate"] + + # Generate boilerplate around minimal project config: + project_config = { + "allowedDomains": ["*"], + "piiConfig": None, + "trustedRelays": [], + "metricExtraction": config, + } + + normalized = normalize_project_config(project_config)["metricExtraction"]["extrapolate"] + assert normalized == config["extrapolate"]