Browse Source

feat(relay): Move histogram outliers to global config (#71004)

Histogram outliers are a static piece of metrics extraction config that
can be moved to global config. This will reduce network traffic, project
config sizes in redis and first and foremost relay in-memory caches.

This PR also fixes a long-standing bug in the outlier config.
Joris Bayer 9 months ago
parent
commit
6346c1751f

+ 1 - 1
requirements-base.txt

@@ -66,7 +66,7 @@ sentry-arroyo>=2.16.5
 sentry-kafka-schemas>=0.1.81
 sentry-ophio==0.2.7
 sentry-redis-tools>=0.1.7
-sentry-relay>=0.8.66
+sentry-relay>=0.8.67
 sentry-sdk>=2.2.1
 snuba-sdk>=2.0.33
 simplejson>=3.17.6

+ 1 - 1
requirements-dev-frozen.txt

@@ -183,7 +183,7 @@ sentry-forked-djangorestframework-stubs==3.15.0.post1
 sentry-kafka-schemas==0.1.81
 sentry-ophio==0.2.7
 sentry-redis-tools==0.1.7
-sentry-relay==0.8.66
+sentry-relay==0.8.67
 sentry-sdk==2.2.1
 sentry-usage-accountant==0.0.10
 simplejson==3.17.6

+ 1 - 1
requirements-frozen.txt

@@ -123,7 +123,7 @@ sentry-arroyo==2.16.5
 sentry-kafka-schemas==0.1.81
 sentry-ophio==0.2.7
 sentry-redis-tools==0.1.7
-sentry-relay==0.8.66
+sentry-relay==0.8.67
 sentry-sdk==2.2.1
 sentry-usage-accountant==0.0.10
 simplejson==3.17.6

+ 1 - 1
src/sentry/relay/config/__init__.py

@@ -1011,7 +1011,7 @@ def _filter_option_to_config_setting(flt: _FilterSpec, setting: str) -> Mapping[
 #: When you increment this version, outdated Relays will stop extracting
 #: transaction metrics.
 #: See https://github.com/getsentry/relay/blob/6181c6e80b9485ed394c40bc860586ae934704e2/relay-dynamic-config/src/metrics.rs#L85
-TRANSACTION_METRICS_EXTRACTION_VERSION = 5
+TRANSACTION_METRICS_EXTRACTION_VERSION = 6
 
 
 class CustomMeasurementSettings(TypedDict):

+ 66 - 24
src/sentry/relay/config/metric_extraction.py

@@ -4,7 +4,7 @@ from collections import defaultdict
 from collections.abc import Sequence
 from dataclasses import dataclass
 from datetime import timedelta
-from typing import Any, TypedDict
+from typing import Any, NotRequired, TypedDict
 
 import sentry_sdk
 from celery.exceptions import SoftTimeLimitExceeded
@@ -42,6 +42,8 @@ from sentry.snuba.metrics.extraction import (
     OnDemandMetricSpecVersioning,
     RuleCondition,
     SpecVersion,
+    TagMapping,
+    TagSpec,
     are_specs_equal,
     should_use_on_demand_metrics,
 )
@@ -57,7 +59,7 @@ logger = logging.getLogger(__name__)
 # GENERIC METRIC EXTRACTION
 
 # Version of the metric extraction config.
-_METRIC_EXTRACTION_VERSION = 3
+_METRIC_EXTRACTION_VERSION = 4
 
 # Maximum number of custom metrics that can be extracted for alerts and widgets with
 # advanced filter expressions.
@@ -831,6 +833,12 @@ _HISTOGRAM_OUTLIERS_TARGET_METRICS = {
     "fcp": "d:transactions/measurements.fcp@millisecond",
 }
 
+_HISTOGRAM_OUTLIERS_SOURCE_FIELDS = {
+    "duration": "event.duration",
+    "lcp": "event.measurements.lcp.value",
+    "fcp": "event.measurements.fcp.value",
+}
+
 
 @dataclass
 class _DefaultThreshold:
@@ -876,8 +884,6 @@ def get_metric_conditional_tagging_rules(
     except ProjectTransactionThreshold.DoesNotExist:
         rules.extend(_threshold_to_rules(_DEFAULT_THRESHOLD, []))
 
-    rules.extend(HISTOGRAM_OUTLIER_RULES)
-
     return rules
 
 
@@ -1395,8 +1401,8 @@ def _parse_percentiles(value: tuple[()] | tuple[str, str, str, str, str]) -> tup
     return p25, p75
 
 
-def _produce_histogram_outliers(query_results: Any) -> Sequence[MetricConditionalTaggingRule]:
-    rules: list[MetricConditionalTaggingRule] = []
+def _produce_histogram_outliers(query_results: Any) -> list[TagMapping]:
+    tags_by_metric: dict[str, list[TagSpec]] = {}
     for row in query_results:
         platform = row["platform"]
         op = row["op"]
@@ -1416,7 +1422,7 @@ def _produce_histogram_outliers(query_results: Any) -> Sequence[MetricConditiona
                 # default values from clickhouse if no data is present
                 continue
 
-            rules.append(
+            tags_by_metric.setdefault(_HISTOGRAM_OUTLIERS_TARGET_METRICS[metric], []).append(
                 {
                     "condition": {
                         "op": "and",
@@ -1427,37 +1433,48 @@ def _produce_histogram_outliers(query_results: Any) -> Sequence[MetricConditiona
                             # See also https://en.wikipedia.org/wiki/Outlier#Tukey's_fences
                             {
                                 "op": "gte",
-                                "name": "event.duration",
+                                "name": _HISTOGRAM_OUTLIERS_SOURCE_FIELDS[metric],
                                 "value": p75 + 3 * abs(p75 - p25),
                             },
                         ],
                     },
-                    "targetMetrics": [_HISTOGRAM_OUTLIERS_TARGET_METRICS[metric]],
-                    "targetTag": "histogram_outlier",
-                    "tagValue": "outlier",
+                    "key": "histogram_outlier",
+                    "value": "outlier",
                 }
             )
 
+    rules: list[TagMapping] = [
+        {"metrics": [metric], "tags": tags} for metric, tags in tags_by_metric.items()
+    ]
+
     rules.append(
         {
-            "condition": {
-                "op": "and",
-                "inner": [
-                    {"op": "gte", "name": "event.duration", "value": 0},
-                ],
-            },
-            "targetMetrics": list(_HISTOGRAM_OUTLIERS_TARGET_METRICS.values()),
-            "targetTag": "histogram_outlier",
-            "tagValue": "inlier",
+            "metrics": list(_HISTOGRAM_OUTLIERS_TARGET_METRICS.values()),
+            "tags": [
+                {
+                    "condition": {
+                        "op": "and",
+                        "inner": [
+                            {"op": "gte", "name": "event.duration", "value": 0},
+                        ],
+                    },
+                    "key": "histogram_outlier",
+                    "value": "inlier",
+                },
+            ],
         }
     )
 
     rules.append(
         {
-            "condition": {"op": "and", "inner": []},
-            "targetMetrics": list(_HISTOGRAM_OUTLIERS_TARGET_METRICS.values()),
-            "targetTag": "histogram_outlier",
-            "tagValue": "outlier",
+            "metrics": list(_HISTOGRAM_OUTLIERS_TARGET_METRICS.values()),
+            "tags": [
+                {
+                    "condition": {"op": "and", "inner": []},
+                    "key": "histogram_outlier",
+                    "value": "outlier",
+                }
+            ],
         }
     )
 
@@ -1492,3 +1509,28 @@ def widget_exceeds_max_specs(
 
 
 HISTOGRAM_OUTLIER_RULES = _produce_histogram_outliers(_HISTOGRAM_OUTLIERS_QUERY_RESULTS)
+
+
+class MetricExtractionGroup(TypedDict):
+    #: Whether a group of globally defined metrics and/or tags is enabled by default for every project.
+    #: This can be overridden in project configs.
+    isEnabled: bool
+    #: List of metrics to extract.
+    metrics: NotRequired[list[MetricSpec]]
+    #: List of tags to apply to previously extracted metrics.
+    tags: NotRequired[list[TagMapping]]
+
+
+class MetricExtractionGroups(TypedDict):
+    groups: dict[str, MetricExtractionGroup]
+
+
+def global_metric_extraction_groups() -> MetricExtractionGroups:
+    return {
+        "groups": {
+            "histogram_outliers": {
+                "isEnabled": True,  # enabled by default
+                "tags": HISTOGRAM_OUTLIER_RULES,
+            }
+        }
+    }

+ 6 - 0
src/sentry/relay/globalconfig.py

@@ -4,6 +4,10 @@ import sentry.options
 from sentry.relay.config import GenericFiltersConfig
 from sentry.relay.config.ai_model_costs import AIModelCosts, ai_model_costs_config
 from sentry.relay.config.measurements import MeasurementsConfig, get_measurements_config
+from sentry.relay.config.metric_extraction import (
+    MetricExtractionGroups,
+    global_metric_extraction_groups,
+)
 from sentry.utils import metrics
 
 # List of options to include in the global config.
@@ -29,6 +33,7 @@ RELAY_OPTIONS: list[str] = [
 class GlobalConfig(TypedDict, total=False):
     measurements: MeasurementsConfig
     aiModelCosts: AIModelCosts
+    metricExtraction: MetricExtractionGroups
     filters: GenericFiltersConfig | None
     options: dict[str, Any]
 
@@ -47,6 +52,7 @@ def get_global_config():
     global_config: GlobalConfig = {
         "measurements": get_measurements_config(),
         "aiModelCosts": ai_model_costs_config(),
+        "metricExtraction": global_metric_extraction_groups(),
     }
 
     filters = get_global_generic_filters()

+ 14 - 0
src/sentry/snuba/metrics/extraction.py

@@ -405,6 +405,20 @@ class MetricSpec(TypedDict):
     tags: NotRequired[Sequence[TagSpec]]
 
 
+class TagMapping(TypedDict):
+    #: A list of Metric Resource Identifiers (MRI) to apply tags to.
+    #:
+    #: Entries in this list can contain wildcards to match metrics with dynamic MRIs.
+    metrics: list[str]
+
+    #: A list of tags to add to the metric.
+    #:
+    #: Tags can be conditional, see `TagSpec` for configuration options. For this reason, it is
+    #: possible to list tag keys multiple times, each with different conditions. The first matching
+    #: condition will be applied.
+    tags: list[TagSpec]
+
+
 def _check_event_type_transaction(
     query: Sequence[QueryToken], is_top_level_call: bool = True
 ) -> bool:

+ 58 - 0
tests/relay_integration/test_metrics_extraction.py

@@ -106,3 +106,61 @@ class MetricsExtractionTest(RelayStoreHelper, TransactionTestCase):
             # Make sure that all the standard strings are part of the list of common strings:
             non_common_strings = strings_emitted - SHARED_STRINGS.keys()
             assert non_common_strings == known_non_common_strings
+
+    def test_histogram_outliers(self):
+        with Feature(
+            {
+                "organizations:transaction-metrics-extraction": True,
+            }
+        ):
+            event_data = {
+                "type": "transaction",
+                "transaction": "foo",
+                "transaction_info": {"source": "url"},  # 'transaction' tag not extracted
+                "timestamp": iso_format(before_now(seconds=1)),
+                "start_timestamp": iso_format(before_now(seconds=2)),
+                "platform": "javascript",
+                "contexts": {
+                    "trace": {
+                        "op": "pageload",
+                        "trace_id": 32 * "b",
+                        "span_id": 16 * "c",
+                        "type": "trace",
+                    }
+                },
+                "user": {"id": 123},
+                "measurements": {
+                    "fcp": {"value": 999999999.0},
+                    "lcp": {"value": 0.0},
+                },
+            }
+
+            settings = {
+                "bootstrap.servers": "127.0.0.1:9092",  # TODO: read from django settings here
+                "group.id": "test-consumer-%s" % uuid.uuid4().hex,
+                "enable.auto.commit": True,
+                "auto.offset.reset": "earliest",
+            }
+
+            consumer = kafka.Consumer(settings)
+            consumer.assign([kafka.TopicPartition("ingest-performance-metrics", 0)])
+
+            self.post_and_retrieve_event(event_data)
+
+            histogram_outlier_tags = {}
+            for _ in range(1000):
+                message = consumer.poll(timeout=1.0)
+                if message is None:
+                    break
+                bucket = json.loads(message.value())
+                try:
+                    histogram_outlier_tags[bucket["name"]] = bucket["tags"]["histogram_outlier"]
+                except KeyError:
+                    pass
+
+            consumer.close()
+            assert histogram_outlier_tags == {
+                "d:transactions/duration@millisecond": "inlier",
+                "d:transactions/measurements.fcp@millisecond": "outlier",
+                "d:transactions/measurements.lcp@millisecond": "inlier",
+            }

+ 783 - 0
tests/sentry/api/endpoints/snapshots/test_relay_globalconfig_v3/test_global_config_histogram_outliers.pysnap

@@ -0,0 +1,783 @@
+---
+created: '2024-05-16T13:23:17.681110+00:00'
+creator: sentry
+source: tests/sentry/api/endpoints/test_relay_globalconfig_v3.py
+---
+groups:
+  histogram_outliers:
+    isEnabled: true
+    tags:
+    - metrics:
+      - d:transactions/duration@millisecond
+      tags:
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: pageload
+          - name: event.platform
+            op: eq
+            value: javascript
+          - name: event.duration
+            op: gte
+            value: 16123.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: navigation
+          - name: event.platform
+            op: eq
+            value: javascript
+          - name: event.duration
+            op: gte
+            value: 4032.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: http.server
+          - name: event.platform
+            op: eq
+            value: python
+          - name: event.duration
+            op: gte
+            value: 383.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: http.server
+          - name: event.platform
+            op: eq
+            value: node
+          - name: event.duration
+            op: gte
+            value: 506.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: http.server
+          - name: event.platform
+            op: eq
+            value: php
+          - name: event.duration
+            op: gte
+            value: 891.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: ui.load
+          - name: event.platform
+            op: eq
+            value: javascript
+          - name: event.duration
+            op: gte
+            value: 199379.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: celery.task
+          - name: event.platform
+            op: eq
+            value: python
+          - name: event.duration
+            op: gte
+            value: 1516.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: rails.request
+          - name: event.platform
+            op: eq
+            value: ruby
+          - name: event.duration
+            op: gte
+            value: 407.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: queue.task.celery
+          - name: event.platform
+            op: eq
+            value: python
+          - name: event.duration
+            op: gte
+            value: 2637.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: function.nextjs
+          - name: event.platform
+            op: eq
+            value: node
+          - name: event.duration
+            op: gte
+            value: 505.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: ui.load
+          - name: event.platform
+            op: eq
+            value: cocoa
+          - name: event.duration
+            op: gte
+            value: 2387.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: http.server
+          - name: event.platform
+            op: eq
+            value: csharp
+          - name: event.duration
+            op: gte
+            value: 325.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: http.server
+          - name: event.platform
+            op: eq
+            value: ruby
+          - name: event.duration
+            op: gte
+            value: 347.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: ui.load
+          - name: event.platform
+            op: eq
+            value: java
+          - name: event.duration
+            op: gte
+            value: 2889.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: http.server
+          - name: event.platform
+            op: eq
+            value: java
+          - name: event.duration
+            op: gte
+            value: 246.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: awslambda.handler
+          - name: event.platform
+            op: eq
+            value: node
+          - name: event.duration
+            op: gte
+            value: 1747.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: serverless.function
+          - name: event.platform
+            op: eq
+            value: python
+          - name: event.duration
+            op: gte
+            value: 393.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: function.aws.lambda
+          - name: event.platform
+            op: eq
+            value: node
+          - name: event.duration
+            op: gte
+            value: 1633.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: default
+          - name: event.platform
+            op: eq
+            value: javascript
+          - name: event.duration
+            op: gte
+            value: 3216.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: function.aws
+          - name: event.platform
+            op: eq
+            value: python
+          - name: event.duration
+            op: gte
+            value: 1464.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: active_job
+          - name: event.platform
+            op: eq
+            value: ruby
+          - name: event.duration
+            op: gte
+            value: 1059.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: navigation
+          - name: event.platform
+            op: eq
+            value: other
+          - name: event.duration
+            op: gte
+            value: 8706.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: queue.active_job
+          - name: event.platform
+            op: eq
+            value: ruby
+          - name: event.duration
+            op: gte
+            value: 4789.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: sidekiq
+          - name: event.platform
+            op: eq
+            value: ruby
+          - name: event.duration
+            op: gte
+            value: 942.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: pageload
+          - name: event.platform
+            op: eq
+            value: other
+          - name: event.duration
+            op: gte
+            value: 3000.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: console.command
+          - name: event.platform
+            op: eq
+            value: php
+          - name: event.duration
+            op: gte
+            value: 1485.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: queue.sidekiq
+          - name: event.platform
+            op: eq
+            value: ruby
+          - name: event.duration
+            op: gte
+            value: 2262.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: transaction
+          - name: event.platform
+            op: eq
+            value: node
+          - name: event.duration
+            op: gte
+            value: 333.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: ui.action
+          - name: event.platform
+            op: eq
+            value: cocoa
+          - name: event.duration
+            op: gte
+            value: 10400.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: default
+          - name: event.platform
+            op: eq
+            value: node
+          - name: event.duration
+            op: gte
+            value: 1686.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: ui.action.click
+          - name: event.platform
+            op: eq
+            value: cocoa
+          - name: event.duration
+            op: gte
+            value: 14519.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: asgi.server
+          - name: event.platform
+            op: eq
+            value: python
+          - name: event.duration
+            op: gte
+            value: 4690.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: http.server
+          - name: event.platform
+            op: eq
+            value: go
+          - name: event.duration
+            op: gte
+            value: 16.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: sentry.test
+          - name: event.platform
+            op: eq
+            value: php
+          - name: event.duration
+            op: gte
+            value: 4.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: websocket.server
+          - name: event.platform
+            op: eq
+            value: ruby
+          - name: event.duration
+            op: gte
+            value: 16.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: ui.action.click
+          - name: event.platform
+            op: eq
+            value: java
+          - name: event.duration
+            op: gte
+            value: 13211.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: http.server
+          - name: event.platform
+            op: eq
+            value: other
+          - name: event.duration
+            op: gte
+            value: 228.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: test
+          - name: event.platform
+            op: eq
+            value: node
+          - name: event.duration
+            op: gte
+            value: 4284.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: gql
+          - name: event.platform
+            op: eq
+            value: node
+          - name: event.duration
+            op: gte
+            value: 492.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: default
+          - name: event.platform
+            op: eq
+            value: python
+          - name: event.duration
+            op: gte
+            value: 253.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: rails.action_cable
+          - name: event.platform
+            op: eq
+            value: ruby
+          - name: event.duration
+            op: gte
+            value: 20.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: queue.process
+          - name: event.platform
+            op: eq
+            value: php
+          - name: event.duration
+            op: gte
+            value: 850.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: websocket.server
+          - name: event.platform
+            op: eq
+            value: python
+          - name: event.duration
+            op: gte
+            value: 24901.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: rq.task
+          - name: event.platform
+            op: eq
+            value: python
+          - name: event.duration
+            op: gte
+            value: 1435.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: task
+          - name: event.platform
+            op: eq
+            value: python
+          - name: event.duration
+            op: gte
+            value: 1317.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: ui.action.swipe
+          - name: event.platform
+            op: eq
+            value: java
+          - name: event.duration
+            op: gte
+            value: 18818.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: queue.task.rq
+          - name: event.platform
+            op: eq
+            value: python
+          - name: event.duration
+            op: gte
+            value: 3313.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: navigation
+          - name: event.platform
+            op: eq
+            value: java
+          - name: event.duration
+            op: gte
+            value: 9647.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: ui.action.scroll
+          - name: event.platform
+            op: eq
+            value: java
+          - name: event.duration
+            op: gte
+            value: 7432.0
+          op: and
+        key: histogram_outlier
+        value: outlier
+    - metrics:
+      - d:transactions/measurements.lcp@millisecond
+      tags:
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: pageload
+          - name: event.platform
+            op: eq
+            value: javascript
+          - name: event.measurements.lcp.value
+            op: gte
+            value: 7941.899538040161
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: pageload
+          - name: event.platform
+            op: eq
+            value: other
+          - name: event.measurements.lcp.value
+            op: gte
+            value: 4589.822045672948
+          op: and
+        key: histogram_outlier
+        value: outlier
+    - metrics:
+      - d:transactions/measurements.fcp@millisecond
+      tags:
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: pageload
+          - name: event.platform
+            op: eq
+            value: javascript
+          - name: event.measurements.fcp.value
+            op: gte
+            value: 5897.500002294778
+          op: and
+        key: histogram_outlier
+        value: outlier
+      - condition:
+          inner:
+          - name: event.contexts.trace.op
+            op: eq
+            value: pageload
+          - name: event.platform
+            op: eq
+            value: other
+          - name: event.measurements.fcp.value
+            op: gte
+            value: 3384.3555060724457
+          op: and
+        key: histogram_outlier
+        value: outlier
+    - metrics:
+      - d:transactions/duration@millisecond
+      - d:transactions/measurements.lcp@millisecond
+      - d:transactions/measurements.fcp@millisecond
+      tags:
+      - condition:
+          inner:
+          - name: event.duration
+            op: gte
+            value: 0
+          op: and
+        key: histogram_outlier
+        value: inlier
+    - metrics:
+      - d:transactions/duration@millisecond
+      - d:transactions/measurements.lcp@millisecond
+      - d:transactions/measurements.fcp@millisecond
+      tags:
+      - condition:
+          inner: []
+          op: and
+        key: histogram_outlier
+        value: outlier

+ 6 - 0
tests/sentry/api/endpoints/test_relay_globalconfig_v3.py

@@ -122,3 +122,9 @@ def test_return_global_config_on_right_version(
 def test_global_config_valid_with_generic_filters():
     config = get_global_config()
     assert config == normalize_global_config(config)
+
+
+@django_db_all
+def test_global_config_histogram_outliers(insta_snapshot):
+    config = get_global_config()
+    insta_snapshot(config["metricExtraction"])

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