Browse Source

ref(metrics layer): Add an aggregate alias mapping (#60203)

The products use consistent aliases for quantile functions. In order to
have one
place for this conversion, add an aggregate alias mapping in the metrics
layer.
Evan Hicks 1 year ago
parent
commit
bbd8abfbde
2 changed files with 74 additions and 0 deletions
  1. 37 0
      src/sentry/snuba/metrics_layer/query.py
  2. 37 0
      tests/snuba/test_metrics_layer.py

+ 37 - 0
src/sentry/snuba/metrics_layer/query.py

@@ -32,6 +32,15 @@ FilterTypes = Union[Column, CurriedFunction, Condition, BooleanCondition]
 ALLOWED_GRANULARITIES = [10, 60, 3600, 86400]
 ALLOWED_GRANULARITIES = sorted(ALLOWED_GRANULARITIES)  # Ensure it's ordered
 
+# These aliases are sent in from the product, and need to be mapped to the actual snuba function
+# Provide a mapping from alias to aggregate/aggregate parameters.
+AGGREGATE_ALIASES = {
+    "p50": ("quantiles", [0.5]),
+    "p75": ("quantiles", [0.75]),
+    "p95": ("quantiles", [0.95]),
+    "p99": ("quantiles", [0.99]),
+}
+
 
 def run_query(request: Request) -> Mapping[str, Any]:
     """
@@ -97,6 +106,10 @@ def run_query(request: Request) -> Mapping[str, Any]:
         )
         raise e
 
+    # Replace any aggregate aliases with the appropriate aggregate
+    aliased_query = _resolve_aggregate_aliases(request.query)
+    request.query = aliased_query
+
     try:
         snuba_results = raw_snql_query(request, request.tenant_ids["referrer"], use_cache=True)
     except Exception as e:
@@ -386,3 +399,27 @@ def _resolve_filters(
 
     new_filters = [resolve_exp(exp) for exp in filters]
     return new_filters, mappings
+
+
+def _resolve_aggregate_aliases(metrics_query: MetricsQuery) -> MetricsQuery:
+    """
+    Replaces any aggregate aliases with the appropriate aggregate.
+    """
+    if isinstance(metrics_query.query, Timeseries):
+        series = metrics_query.query
+        if series.aggregate in AGGREGATE_ALIASES:
+            aggregate, parameters = AGGREGATE_ALIASES[series.aggregate]
+            series = series.set_aggregate(aggregate, parameters)
+            return metrics_query.set_query(series)
+
+    elif isinstance(metrics_query.query, Formula):
+        parameters = metrics_query.query.parameters
+        for i, p in enumerate(parameters):
+            if isinstance(p, Timeseries):
+                if p.aggregate in AGGREGATE_ALIASES:
+                    aggregate, parameters = AGGREGATE_ALIASES[p.aggregate]
+                    parameters[i] = p.set_aggregate(aggregate, parameters)
+
+        return metrics_query.set_query(metrics_query.query.set_parameters(parameters))
+
+    return metrics_query

+ 37 - 0
tests/snuba/test_metrics_layer.py

@@ -491,3 +491,40 @@ class SnQLTest(TestCase, BaseMetricsTestCase):
 
         assert len(result["data"]) == 10
         assert result["totals"]["aggregate_value"] == 1.0
+
+    def test_aggregate_aliases(self) -> None:
+        query = MetricsQuery(
+            query=Timeseries(
+                metric=Metric(
+                    "transaction.duration",
+                    TransactionMRI.DURATION.value,
+                ),
+                aggregate="p95",
+            ),
+            start=self.hour_ago,
+            end=self.now,
+            rollup=Rollup(interval=60, granularity=60),
+            scope=MetricsScope(
+                org_ids=[self.org_id],
+                project_ids=[self.project.id],
+                use_case_id=UseCaseID.TRANSACTIONS.value,
+            ),
+        )
+
+        request = Request(
+            dataset="generic_metrics",
+            app_id="tests",
+            query=query,
+            tenant_ids={"referrer": "metrics.testing.test", "organization_id": self.org_id},
+        )
+        result = run_query(request)
+        assert len(result["data"]) == 10
+        rows = result["data"]
+        for i in range(10):
+            assert rows[i]["aggregate_value"] == [i]
+            assert (
+                rows[i]["time"]
+                == (
+                    self.hour_ago.replace(second=0, microsecond=0) + timedelta(minutes=1 * i)
+                ).isoformat()
+            )