Browse Source

feat(eap): Add a method that powers metrics explorer with EAP (#76208)

Look, this is pretty gross. It short-circuit returns some things,
doesn't implement all of MQL, but it's a way to at least query EAP in
production. We can start implementing the rest with net-new RPCs for
things like tag discovery later.
colin-sentry 6 months ago
parent
commit
4ec5694c24

+ 1 - 0
requirements-base.txt

@@ -67,6 +67,7 @@ rfc3986-validator>=0.1.1
 sentry-arroyo>=2.16.5
 sentry-arroyo>=2.16.5
 sentry-kafka-schemas>=0.1.106
 sentry-kafka-schemas>=0.1.106
 sentry-ophio==0.2.7
 sentry-ophio==0.2.7
+sentry-protos>=0.1.3
 sentry-redis-tools>=0.1.7
 sentry-redis-tools>=0.1.7
 sentry-relay>=0.9.1
 sentry-relay>=0.9.1
 sentry-sdk>=2.12.0
 sentry-sdk>=2.12.0

+ 2 - 0
requirements-dev-frozen.txt

@@ -69,6 +69,7 @@ google-resumable-media==2.7.0
 googleapis-common-protos==1.63.2
 googleapis-common-protos==1.63.2
 grpc-google-iam-v1==0.13.1
 grpc-google-iam-v1==0.13.1
 grpc-interceptor==0.15.4
 grpc-interceptor==0.15.4
+grpc-stubs==1.53.0.5
 grpcio==1.60.1
 grpcio==1.60.1
 grpcio-status==1.60.1
 grpcio-status==1.60.1
 h11==0.13.0
 h11==0.13.0
@@ -184,6 +185,7 @@ sentry-forked-django-stubs==5.0.4.post1
 sentry-forked-djangorestframework-stubs==3.15.0.post1
 sentry-forked-djangorestframework-stubs==3.15.0.post1
 sentry-kafka-schemas==0.1.106
 sentry-kafka-schemas==0.1.106
 sentry-ophio==0.2.7
 sentry-ophio==0.2.7
+sentry-protos==0.1.3
 sentry-redis-tools==0.1.7
 sentry-redis-tools==0.1.7
 sentry-relay==0.9.1
 sentry-relay==0.9.1
 sentry-sdk==2.12.0
 sentry-sdk==2.12.0

+ 2 - 0
requirements-frozen.txt

@@ -57,6 +57,7 @@ google-resumable-media==2.7.0
 googleapis-common-protos==1.63.2
 googleapis-common-protos==1.63.2
 grpc-google-iam-v1==0.13.1
 grpc-google-iam-v1==0.13.1
 grpc-interceptor==0.15.4
 grpc-interceptor==0.15.4
+grpc-stubs==1.53.0.5
 grpcio==1.60.1
 grpcio==1.60.1
 grpcio-status==1.60.1
 grpcio-status==1.60.1
 h11==0.14.0
 h11==0.14.0
@@ -125,6 +126,7 @@ s3transfer==0.10.0
 sentry-arroyo==2.16.5
 sentry-arroyo==2.16.5
 sentry-kafka-schemas==0.1.106
 sentry-kafka-schemas==0.1.106
 sentry-ophio==0.2.7
 sentry-ophio==0.2.7
+sentry-protos==0.1.3
 sentry-redis-tools==0.1.7
 sentry-redis-tools==0.1.7
 sentry-relay==0.9.1
 sentry-relay==0.9.1
 sentry-sdk==2.12.0
 sentry-sdk==2.12.0

+ 19 - 0
src/sentry/api/endpoints/organization_metrics_details.py

@@ -1,6 +1,7 @@
 from rest_framework.request import Request
 from rest_framework.request import Request
 from rest_framework.response import Response
 from rest_framework.response import Response
 
 
+from sentry import features
 from sentry.api.api_owners import ApiOwner
 from sentry.api.api_owners import ApiOwner
 from sentry.api.api_publish_status import ApiPublishStatus
 from sentry.api.api_publish_status import ApiPublishStatus
 from sentry.api.base import region_silo_endpoint
 from sentry.api.base import region_silo_endpoint
@@ -27,6 +28,24 @@ class OrganizationMetricsDetailsEndpoint(OrganizationEndpoint):
                 {"detail": "You must supply at least one project to see its metrics"}, status=404
                 {"detail": "You must supply at least one project to see its metrics"}, status=404
             )
             )
 
 
+        if all(
+            features.has("projects:use-eap-spans-for-metrics-explorer", project)
+            for project in projects
+        ):
+            return Response(
+                [
+                    {
+                        "type": "d",
+                        "name": "measurement",
+                        "unit": "none",
+                        "mri": "d:eap/measurement@none",
+                        "operations": ["sum", "avg", "p50", "p95", "p99", "count"],
+                        "projectIds": [project.id for project in projects],
+                        "blockingStatus": [],
+                    }
+                ]
+            )
+
         metrics = get_metrics_meta(
         metrics = get_metrics_meta(
             organization=organization, projects=projects, use_case_ids=get_use_case_ids(request)
             organization=organization, projects=projects, use_case_ids=get_use_case_ids(request)
         )
         )

+ 22 - 2
src/sentry/api/endpoints/organization_metrics_query.py

@@ -4,7 +4,7 @@ from datetime import datetime, timedelta, timezone
 from rest_framework.request import Request
 from rest_framework.request import Request
 from rest_framework.response import Response
 from rest_framework.response import Response
 
 
-from sentry import options
+from sentry import features, options
 from sentry.api.api_owners import ApiOwner
 from sentry.api.api_owners import ApiOwner
 from sentry.api.api_publish_status import ApiPublishStatus
 from sentry.api.api_publish_status import ApiPublishStatus
 from sentry.api.base import region_silo_endpoint
 from sentry.api.base import region_silo_endpoint
@@ -17,6 +17,7 @@ from sentry.sentry_metrics.querying.data import (
     MQLQuery,
     MQLQuery,
     run_queries,
     run_queries,
 )
 )
+from sentry.sentry_metrics.querying.eap import mql_eap_bridge
 from sentry.sentry_metrics.querying.errors import (
 from sentry.sentry_metrics.querying.errors import (
     InvalidMetricsQueryError,
     InvalidMetricsQueryError,
     LatestReleaseNotFoundError,
     LatestReleaseNotFoundError,
@@ -156,6 +157,7 @@ class OrganizationMetricsQueryEndpoint(OrganizationEndpoint):
             start, end = get_date_range_from_params(request.GET)
             start, end = get_date_range_from_params(request.GET)
             interval = self._interval_from_request(request)
             interval = self._interval_from_request(request)
             mql_queries = self._mql_queries_from_request(request)
             mql_queries = self._mql_queries_from_request(request)
+            projects = self.get_projects(request, organization)
 
 
             metrics.incr(
             metrics.incr(
                 key="ddm.metrics_api.query",
                 key="ddm.metrics_api.query",
@@ -166,13 +168,31 @@ class OrganizationMetricsQueryEndpoint(OrganizationEndpoint):
                 },
                 },
             )
             )
 
 
+            if all(
+                features.has("projects:use-eap-spans-for-metrics-explorer", project)
+                for project in projects
+            ):
+                if len(mql_queries) == 1 and "a" in mql_queries[0].sub_queries:
+                    subquery = mql_queries[0].sub_queries["a"]
+                    if "d:eap/" in subquery.mql:
+                        res_data = mql_eap_bridge.make_eap_request(
+                            subquery.mql,
+                            start,
+                            end,
+                            interval,
+                            organization,
+                            projects,
+                            Referrer.API_ORGANIZATION_METRICS_EAP_QUERY.value,
+                        )
+                        return Response(status=200, data=res_data)
+
             results = run_queries(
             results = run_queries(
                 mql_queries=mql_queries,
                 mql_queries=mql_queries,
                 start=start,
                 start=start,
                 end=end,
                 end=end,
                 interval=interval,
                 interval=interval,
                 organization=organization,
                 organization=organization,
-                projects=self.get_projects(request, organization),
+                projects=projects,
                 environments=self.get_environments(request, organization),
                 environments=self.get_environments(request, organization),
                 referrer=Referrer.API_ORGANIZATION_METRICS_QUERY.value,
                 referrer=Referrer.API_ORGANIZATION_METRICS_QUERY.value,
                 query_type=self._get_query_type_from_request(request),
                 query_type=self._get_query_type_from_request(request),

+ 23 - 0
src/sentry/api/endpoints/organization_metrics_tag_details.py

@@ -2,6 +2,7 @@ from rest_framework.exceptions import NotFound, ParseError
 from rest_framework.request import Request
 from rest_framework.request import Request
 from rest_framework.response import Response
 from rest_framework.response import Response
 
 
+from sentry import features
 from sentry.api.api_owners import ApiOwner
 from sentry.api.api_owners import ApiOwner
 from sentry.api.api_publish_status import ApiPublishStatus
 from sentry.api.api_publish_status import ApiPublishStatus
 from sentry.api.base import region_silo_endpoint
 from sentry.api.base import region_silo_endpoint
@@ -35,6 +36,28 @@ class OrganizationMetricsTagDetailsEndpoint(OrganizationEndpoint):
                 {"detail": "You must supply at least one project to see its metrics"}, status=404
                 {"detail": "You must supply at least one project to see its metrics"}, status=404
             )
             )
 
 
+        if all(
+            features.has("projects:use-eap-spans-for-metrics-explorer", project)
+            for project in projects
+        ):
+            if len(metric_names) == 1 and metric_names[0].startswith("d:eap"):
+                # TODO hack for EAP, hardcode some metric names
+                if tag_name == "color":
+                    return Response(
+                        [
+                            {"key": tag_name, "value": "red"},
+                            {"key": tag_name, "value": "blue"},
+                            {"key": tag_name, "value": "green"},
+                        ]
+                    )
+                if tag_name == "location":
+                    return Response(
+                        [
+                            {"key": tag_name, "value": "mobile"},
+                            {"key": tag_name, "value": "frontend"},
+                            {"key": tag_name, "value": "backend"},
+                        ]
+                    )
         try:
         try:
             mris = convert_metric_names_to_mris(metric_names)
             mris = convert_metric_names_to_mris(metric_names)
             tag_values: set[str] = set()
             tag_values: set[str] = set()

+ 9 - 0
src/sentry/api/endpoints/organization_metrics_tags.py

@@ -5,6 +5,7 @@ from rest_framework.exceptions import NotFound, ParseError
 from rest_framework.request import Request
 from rest_framework.request import Request
 from rest_framework.response import Response
 from rest_framework.response import Response
 
 
+from sentry import features
 from sentry.api.api_owners import ApiOwner
 from sentry.api.api_owners import ApiOwner
 from sentry.api.api_publish_status import ApiPublishStatus
 from sentry.api.api_publish_status import ApiPublishStatus
 from sentry.api.base import region_silo_endpoint
 from sentry.api.base import region_silo_endpoint
@@ -52,6 +53,14 @@ class OrganizationMetricsTagsEndpoint(OrganizationEndpoint):
         if not is_mri(metric_name):
         if not is_mri(metric_name):
             raise BadRequest(message="Please provide a valid MRI to query a metric's tags.")
             raise BadRequest(message="Please provide a valid MRI to query a metric's tags.")
 
 
+        if all(
+            features.has("projects:use-eap-spans-for-metrics-explorer", project)
+            for project in projects
+        ):
+            if metric_name.startswith("d:eap"):
+                # TODO hack for EAP, return a fixed list
+                return Response([Tag(key="color"), Tag(key="location")])
+
         try:
         try:
             if metric_name.startswith("e:"):
             if metric_name.startswith("e:"):
                 # If metric_name starts with "e:", and therefore is a derived metric, use the old get_all_tags functionality
                 # If metric_name starts with "e:", and therefore is a derived metric, use the old get_all_tags functionality

+ 2 - 0
src/sentry/features/temporary.py

@@ -539,6 +539,8 @@ def register_temporary_features(manager: FeatureManager):
     manager.add("projects:span-metrics-extraction", ProjectFeature, FeatureHandlerStrategy.INTERNAL, api_expose=True)
     manager.add("projects:span-metrics-extraction", ProjectFeature, FeatureHandlerStrategy.INTERNAL, api_expose=True)
     manager.add("projects:span-metrics-extraction-addons", ProjectFeature, FeatureHandlerStrategy.INTERNAL, api_expose=False)
     manager.add("projects:span-metrics-extraction-addons", ProjectFeature, FeatureHandlerStrategy.INTERNAL, api_expose=False)
     manager.add("projects:relay-otel-endpoint", ProjectFeature, FeatureHandlerStrategy.OPTIONS, api_expose=False)
     manager.add("projects:relay-otel-endpoint", ProjectFeature, FeatureHandlerStrategy.OPTIONS, api_expose=False)
+    # EAP: extremely experimental flag that makes DDM page use EAP tables
+    manager.add("projects:use-eap-spans-for-metrics-explorer", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
 
 
     # Project plugin features
     # Project plugin features
     manager.add("projects:plugins", ProjectPluginFeature, FeatureHandlerStrategy.INTERNAL, default=True, api_expose=True)
     manager.add("projects:plugins", ProjectPluginFeature, FeatureHandlerStrategy.INTERNAL, default=True, api_expose=True)

+ 5 - 0
src/sentry/sentry_metrics/querying/eap/README.md

@@ -0,0 +1,5 @@
+We would like to move metrics querying to a span-based system backed by `eap_spans`, part of the Events Analytics Platform work.
+
+This module facilitates some hacky initial MQL -> GRPC logic, used as a POC for those efforts.
+
+You should not consider this to be production-ready yet.

+ 0 - 0
src/sentry/sentry_metrics/querying/eap/__init__.py


Some files were not shown because too many files changed in this diff