diff --git a/src/sentry/metrics/composite_experimental.py b/src/sentry/metrics/composite_experimental.py deleted file mode 100644 index 8e080f12c81a4f..00000000000000 --- a/src/sentry/metrics/composite_experimental.py +++ /dev/null @@ -1,151 +0,0 @@ -from typing import Any - -from sentry.metrics.base import MetricsBackend, Tags -from sentry.metrics.dummy import DummyMetricsBackend -from sentry.metrics.minimetrics import MiniMetricsMetricsBackend -from sentry.utils.imports import import_string - -__all__ = ["CompositeExperimentalMetricsBackend"] - - -class CompositeExperimentalMetricsBackend(MetricsBackend): - def __init__(self, **kwargs: Any): - super().__init__() - self._initialize_backends( - kwargs.pop("primary_backend", None), kwargs.pop("primary_backend_args", {}) - ) - self._deny_prefixes = tuple(kwargs.pop("deny_prefixes", [])) - - def _initialize_backends( - self, primary_backend: str | None, primary_backend_args: dict[str, Any] - ): - # If we don't have a primary metrics backend we default to the dummy, which won't do anything. - if primary_backend is None: - self._primary_backend: MetricsBackend = DummyMetricsBackend() - else: - cls: type[MetricsBackend] = import_string(primary_backend) - self._primary_backend = cls(**primary_backend_args) - - self._minimetrics: MiniMetricsMetricsBackend = MiniMetricsMetricsBackend() - - def _is_denied(self, key: str) -> bool: - return key.startswith(self._deny_prefixes) - - @staticmethod - def _minimetrics_sample_rate() -> float: - # Previously bound to options.get("delightful_metrics.minimetrics_sample_rate") - # but this option check was resulting in excessive cache misses somehow. - - return 1.0 - - def incr( - self, - key: str, - instance: str | None = None, - tags: Tags | None = None, - amount: float | int = 1, - sample_rate: float = 1, - unit: str | None = None, - stacklevel: int = 0, - ) -> None: - self._primary_backend.incr(key, instance, tags, amount, sample_rate, unit) - if not self._is_denied(key): - self._minimetrics.incr( - key, - instance, - tags, - amount, - self._minimetrics_sample_rate(), - unit, - stacklevel=stacklevel + 1, - ) - - def timing( - self, - key: str, - value: float, - instance: str | None = None, - tags: Tags | None = None, - sample_rate: float = 1, - stacklevel: int = 0, - ) -> None: - self._primary_backend.timing(key, value, instance, tags, sample_rate) - if not self._is_denied(key): - self._minimetrics.timing( - key, - value, - instance, - tags, - self._minimetrics_sample_rate(), - stacklevel=stacklevel + 1, - ) - - def gauge( - self, - key: str, - value: float, - instance: str | None = None, - tags: Tags | None = None, - sample_rate: float = 1, - unit: str | None = None, - stacklevel: int = 0, - ) -> None: - self._primary_backend.gauge(key, value, instance, tags, sample_rate, unit) - if not self._is_denied(key): - self._minimetrics.gauge( - key, - value, - instance, - tags, - self._minimetrics_sample_rate(), - unit, - stacklevel=stacklevel + 1, - ) - - def distribution( - self, - key: str, - value: float, - instance: str | None = None, - tags: Tags | None = None, - sample_rate: float = 1, - unit: str | None = None, - stacklevel: int = 0, - ) -> None: - self._primary_backend.distribution(key, value, instance, tags, sample_rate, unit) - # We share the same option between timing and distribution, since they are both distribution - # metrics. - if not self._is_denied(key): - self._minimetrics.distribution( - key, - value, - instance, - tags, - self._minimetrics_sample_rate(), - unit, - stacklevel=stacklevel + 1, - ) - - def event( - self, - title: str, - message: str, - alert_type: str | None = None, - aggregation_key: str | None = None, - source_type_name: str | None = None, - priority: str | None = None, - instance: str | None = None, - tags: Tags | None = None, - stacklevel: int = 0, - ) -> None: - self._primary_backend.event( - title, - message, - alert_type, - aggregation_key, - source_type_name, - priority, - instance, - tags, - stacklevel + 1, - ) diff --git a/src/sentry/metrics/minimetrics.py b/src/sentry/metrics/minimetrics.py deleted file mode 100644 index 446b878d002bbb..00000000000000 --- a/src/sentry/metrics/minimetrics.py +++ /dev/null @@ -1,107 +0,0 @@ -import random -from datetime import datetime, timedelta, timezone - -import sentry_sdk -from sentry_sdk.metrics import metrics_noop -from sentry_sdk.tracing import Span - -from sentry.metrics.base import MetricsBackend, Tags - - -def _attach_tags(span: Span, tags: Tags | None) -> None: - if tags: - for tag_key, tag_value in tags.items(): - span.set_data(tag_key, tag_value) - - -@metrics_noop -def _set_metric_on_span(key: str, value: float | int, op: str, tags: Tags | None = None) -> None: - span_or_tx = sentry_sdk.get_current_span() - if span_or_tx is None: - return - - span_or_tx.set_data(key, value) - _attach_tags(span_or_tx, tags) - - -class MiniMetricsMetricsBackend(MetricsBackend): - @staticmethod - def _keep_metric(sample_rate: float) -> bool: - return random.random() < sample_rate - - def incr( - self, - key: str, - instance: str | None = None, - tags: Tags | None = None, - amount: float | int = 1, - sample_rate: float = 1, - unit: str | None = None, - stacklevel: int = 0, - ) -> None: - if self._keep_metric(sample_rate): - _set_metric_on_span(key=key, value=amount, op="incr", tags=tags) - - def timing( - self, - key: str, - value: float, - instance: str | None = None, - tags: Tags | None = None, - sample_rate: float = 1, - stacklevel: int = 0, - ) -> None: - if self._keep_metric(sample_rate): - span_or_tx = sentry_sdk.get_current_span() - if span_or_tx is None: - return - - if span_or_tx.op == key: - _attach_tags(span_or_tx, tags) - return - - timestamp = datetime.now(timezone.utc) - start_timestamp = timestamp - timedelta(seconds=value) - span = span_or_tx.start_child(op=key, start_timestamp=start_timestamp) - _attach_tags(span, tags) - span.finish(end_timestamp=timestamp) - - def gauge( - self, - key: str, - value: float, - instance: str | None = None, - tags: Tags | None = None, - sample_rate: float = 1, - unit: str | None = None, - stacklevel: int = 0, - ) -> None: - if self._keep_metric(sample_rate): - _set_metric_on_span(key=key, value=value, op="gauge", tags=tags) - - def distribution( - self, - key: str, - value: float, - instance: str | None = None, - tags: Tags | None = None, - sample_rate: float = 1, - unit: str | None = None, - stacklevel: int = 0, - ) -> None: - if self._keep_metric(sample_rate): - _set_metric_on_span(key=key, value=value, op="distribution", tags=tags) - - def event( - self, - title: str, - message: str, - alert_type: str | None = None, - aggregation_key: str | None = None, - source_type_name: str | None = None, - priority: str | None = None, - instance: str | None = None, - tags: Tags | None = None, - stacklevel: int = 0, - ) -> None: - pass diff --git a/tests/sentry/metrics/test_minimetrics.py b/tests/sentry/metrics/test_minimetrics.py deleted file mode 100644 index 4091b7f4d464ac..00000000000000 --- a/tests/sentry/metrics/test_minimetrics.py +++ /dev/null @@ -1,197 +0,0 @@ -from datetime import datetime, timedelta -from unittest import mock - -import pytest -import sentry_sdk -import sentry_sdk.scope -from sentry_sdk import Client, Transport - -from sentry.metrics.composite_experimental import CompositeExperimentalMetricsBackend -from sentry.metrics.minimetrics import MiniMetricsMetricsBackend -from sentry.testutils.helpers import override_options - - -def full_flush(scope): - # first flush flushes the metrics - scope.client.flush() - - # second flush should really not do anything unless the first - # flush accidentally created more metrics - scope.client.flush() - - -class DummyTransport(Transport): - def __init__(self, options): - self.captured = [] - - def capture_envelope(self, envelope): - self.captured.append(envelope) - - def get_spans(self): - tx = self.get_transaction() - if tx is None: - return [] - - return tx["spans"] - - def get_transaction(self): - for envelope in self.captured: - for item in envelope.items: - if item.headers.get("type") == "transaction": - return item.payload.json - - -@pytest.fixture(scope="function") -def scope(): - scope = sentry_sdk.Scope( - ty=sentry_sdk.scope.ScopeType.CURRENT, - client=Client( - dsn="http://foo@example.invalid/42", - transport=DummyTransport, - traces_sample_rate=1.0, - ), - ) - with sentry_sdk.scope.use_scope(scope): - yield scope - - -@pytest.fixture(scope="function") -def backend(): - # Make sure we also patch the global metrics backend as the backend - # will forward internal metrics to it (back into itself). If we don't - # set this up correctly, we might accidentally break our recursion - # protection and not see these tests fail. - rv = MiniMetricsMetricsBackend(prefix="sentrytest.") - with mock.patch("sentry.utils.metrics.backend", new=rv): - yield rv - - -def test_incr(backend, scope): - with scope.start_transaction(): - with scope.start_span(op="test"): - backend.incr(key="foo") - - full_flush(scope) - (span,) = scope.client.transport.get_spans() - - assert span["op"] == "test" - assert span["data"]["foo"] == 1 - - -def test_incr_with_tag(backend, scope): - with scope.start_transaction(): - with scope.start_span(op="test"): - backend.incr(key="foo", tags={"x": "y"}) - - full_flush(scope) - (span,) = scope.client.transport.get_spans() - - assert span["op"] == "test" - assert span["data"]["foo"] == 1 - assert span["data"]["x"] == "y" - - -def test_incr_multi(backend, scope): - with scope.start_transaction(): - with scope.start_span(op="test"): - backend.incr(key="foo", tags={"x": "y"}) - backend.incr(key="foo", tags={"x": "z"}) - - full_flush(scope) - (span,) = scope.client.transport.get_spans() - - assert span["op"] == "test" - assert span["data"]["foo"] == 1 # NB: SDK has no get_data() -> incr impossible - assert span["data"]["x"] == "z" - - -def test_gauge(backend, scope): - with scope.start_transaction(): - with scope.start_span(op="test"): - backend.gauge(key="foo", value=0) - backend.gauge(key="foo", value=42.0) - - full_flush(scope) - (span,) = scope.client.transport.get_spans() - - assert span["op"] == "test" - assert span["data"]["foo"] == 42.0 - - -def test_distribution(backend, scope): - with scope.start_transaction(): - with scope.start_span(op="test"): - backend.distribution(key="foo", value=0) - backend.distribution(key="foo", value=42.0) - - full_flush(scope) - (span,) = scope.client.transport.get_spans() - - assert span["op"] == "test" - assert span["data"]["foo"] == 42.0 - - -def test_timing(backend, scope): - with scope.start_transaction(): - with scope.start_span(op="test"): - backend.timing(key="foo", value=42.1, tags={"x": "y"}) - - full_flush(scope) - (parent, child) = scope.client.transport.get_spans() - - assert parent["op"] == "test" - assert child["op"] == "foo" - assert child["data"]["x"] == "y" - - duration = datetime.fromisoformat(child["timestamp"]) - datetime.fromisoformat( - child["start_timestamp"] - ) - assert duration == timedelta(seconds=42.1) - - -def test_timing_duplicate(backend, scope): - with scope.start_transaction(): - # We often manually track a span + a timer with same name. In this case - # we want no additional span. - with scope.start_span(op="test"): - backend.timing(key="test", value=42.0, tags={"x": "y"}) - - full_flush(scope) - (span,) = scope.client.transport.get_spans() - - assert span["op"] == "test" - assert "test" not in span["data"] - assert span["data"]["x"] == "y" - - # NB: Explicit timing is discarded - - -def test_no_transaction(backend, scope): - backend.incr(key="foo") - - full_flush(scope) - assert not scope.client.transport.get_spans() - - -@override_options({"delightful_metrics.minimetrics_sample_rate": 1.0}) -def test_composite_backend_does_not_recurse(scope): - composite_backend = CompositeExperimentalMetricsBackend( - primary_backend="sentry.metrics.dummy.DummyMetricsBackend" - ) - accessed = set() - - class TrackingCompositeBackend: - def __getattr__(self, name): - assert name not in accessed - accessed.add(name) - return getattr(composite_backend, name) - - # make sure the backend feeds back to itself - with mock.patch("sentry.utils.metrics.backend", new=TrackingCompositeBackend()) as backend: - with scope.start_transaction(): - with scope.start_span(op="test"): - backend.incr(key="sentrytest.composite", tags={"x": "bar"}) - full_flush(scope) - - (span,) = scope.client.transport.get_spans() - assert span["data"]["sentrytest.composite"] == 1