Skip to content

Commit d2a0a58

Browse files
getsentry-botantonpirker
authored andcommitted
Revert "Remove minimetrics (#93595)"
This reverts commit 0b72ce7. Co-authored-by: antonpirker <[email protected]>
1 parent 333cd43 commit d2a0a58

File tree

3 files changed

+455
-0
lines changed

3 files changed

+455
-0
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
from typing import Any
2+
3+
from sentry.metrics.base import MetricsBackend, Tags
4+
from sentry.metrics.dummy import DummyMetricsBackend
5+
from sentry.metrics.minimetrics import MiniMetricsMetricsBackend
6+
from sentry.utils.imports import import_string
7+
8+
__all__ = ["CompositeExperimentalMetricsBackend"]
9+
10+
11+
class CompositeExperimentalMetricsBackend(MetricsBackend):
12+
def __init__(self, **kwargs: Any):
13+
super().__init__()
14+
self._initialize_backends(
15+
kwargs.pop("primary_backend", None), kwargs.pop("primary_backend_args", {})
16+
)
17+
self._deny_prefixes = tuple(kwargs.pop("deny_prefixes", []))
18+
19+
def _initialize_backends(
20+
self, primary_backend: str | None, primary_backend_args: dict[str, Any]
21+
):
22+
# If we don't have a primary metrics backend we default to the dummy, which won't do anything.
23+
if primary_backend is None:
24+
self._primary_backend: MetricsBackend = DummyMetricsBackend()
25+
else:
26+
cls: type[MetricsBackend] = import_string(primary_backend)
27+
self._primary_backend = cls(**primary_backend_args)
28+
29+
self._minimetrics: MiniMetricsMetricsBackend = MiniMetricsMetricsBackend()
30+
31+
def _is_denied(self, key: str) -> bool:
32+
return key.startswith(self._deny_prefixes)
33+
34+
@staticmethod
35+
def _minimetrics_sample_rate() -> float:
36+
# Previously bound to options.get("delightful_metrics.minimetrics_sample_rate")
37+
# but this option check was resulting in excessive cache misses somehow.
38+
39+
return 1.0
40+
41+
def incr(
42+
self,
43+
key: str,
44+
instance: str | None = None,
45+
tags: Tags | None = None,
46+
amount: float | int = 1,
47+
sample_rate: float = 1,
48+
unit: str | None = None,
49+
stacklevel: int = 0,
50+
) -> None:
51+
self._primary_backend.incr(key, instance, tags, amount, sample_rate, unit)
52+
if not self._is_denied(key):
53+
self._minimetrics.incr(
54+
key,
55+
instance,
56+
tags,
57+
amount,
58+
self._minimetrics_sample_rate(),
59+
unit,
60+
stacklevel=stacklevel + 1,
61+
)
62+
63+
def timing(
64+
self,
65+
key: str,
66+
value: float,
67+
instance: str | None = None,
68+
tags: Tags | None = None,
69+
sample_rate: float = 1,
70+
stacklevel: int = 0,
71+
) -> None:
72+
self._primary_backend.timing(key, value, instance, tags, sample_rate)
73+
if not self._is_denied(key):
74+
self._minimetrics.timing(
75+
key,
76+
value,
77+
instance,
78+
tags,
79+
self._minimetrics_sample_rate(),
80+
stacklevel=stacklevel + 1,
81+
)
82+
83+
def gauge(
84+
self,
85+
key: str,
86+
value: float,
87+
instance: str | None = None,
88+
tags: Tags | None = None,
89+
sample_rate: float = 1,
90+
unit: str | None = None,
91+
stacklevel: int = 0,
92+
) -> None:
93+
self._primary_backend.gauge(key, value, instance, tags, sample_rate, unit)
94+
if not self._is_denied(key):
95+
self._minimetrics.gauge(
96+
key,
97+
value,
98+
instance,
99+
tags,
100+
self._minimetrics_sample_rate(),
101+
unit,
102+
stacklevel=stacklevel + 1,
103+
)
104+
105+
def distribution(
106+
self,
107+
key: str,
108+
value: float,
109+
instance: str | None = None,
110+
tags: Tags | None = None,
111+
sample_rate: float = 1,
112+
unit: str | None = None,
113+
stacklevel: int = 0,
114+
) -> None:
115+
self._primary_backend.distribution(key, value, instance, tags, sample_rate, unit)
116+
# We share the same option between timing and distribution, since they are both distribution
117+
# metrics.
118+
if not self._is_denied(key):
119+
self._minimetrics.distribution(
120+
key,
121+
value,
122+
instance,
123+
tags,
124+
self._minimetrics_sample_rate(),
125+
unit,
126+
stacklevel=stacklevel + 1,
127+
)
128+
129+
def event(
130+
self,
131+
title: str,
132+
message: str,
133+
alert_type: str | None = None,
134+
aggregation_key: str | None = None,
135+
source_type_name: str | None = None,
136+
priority: str | None = None,
137+
instance: str | None = None,
138+
tags: Tags | None = None,
139+
stacklevel: int = 0,
140+
) -> None:
141+
self._primary_backend.event(
142+
title,
143+
message,
144+
alert_type,
145+
aggregation_key,
146+
source_type_name,
147+
priority,
148+
instance,
149+
tags,
150+
stacklevel + 1,
151+
)

src/sentry/metrics/minimetrics.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import random
2+
from datetime import datetime, timedelta, timezone
3+
4+
import sentry_sdk
5+
from sentry_sdk.metrics import metrics_noop
6+
from sentry_sdk.tracing import Span
7+
8+
from sentry.metrics.base import MetricsBackend, Tags
9+
10+
11+
def _attach_tags(span: Span, tags: Tags | None) -> None:
12+
if tags:
13+
for tag_key, tag_value in tags.items():
14+
span.set_data(tag_key, tag_value)
15+
16+
17+
@metrics_noop
18+
def _set_metric_on_span(key: str, value: float | int, op: str, tags: Tags | None = None) -> None:
19+
span_or_tx = sentry_sdk.get_current_span()
20+
if span_or_tx is None:
21+
return
22+
23+
span_or_tx.set_data(key, value)
24+
_attach_tags(span_or_tx, tags)
25+
26+
27+
class MiniMetricsMetricsBackend(MetricsBackend):
28+
@staticmethod
29+
def _keep_metric(sample_rate: float) -> bool:
30+
return random.random() < sample_rate
31+
32+
def incr(
33+
self,
34+
key: str,
35+
instance: str | None = None,
36+
tags: Tags | None = None,
37+
amount: float | int = 1,
38+
sample_rate: float = 1,
39+
unit: str | None = None,
40+
stacklevel: int = 0,
41+
) -> None:
42+
if self._keep_metric(sample_rate):
43+
_set_metric_on_span(key=key, value=amount, op="incr", tags=tags)
44+
45+
def timing(
46+
self,
47+
key: str,
48+
value: float,
49+
instance: str | None = None,
50+
tags: Tags | None = None,
51+
sample_rate: float = 1,
52+
stacklevel: int = 0,
53+
) -> None:
54+
if self._keep_metric(sample_rate):
55+
span_or_tx = sentry_sdk.get_current_span()
56+
if span_or_tx is None:
57+
return
58+
59+
if span_or_tx.op == key:
60+
_attach_tags(span_or_tx, tags)
61+
return
62+
63+
timestamp = datetime.now(timezone.utc)
64+
start_timestamp = timestamp - timedelta(seconds=value)
65+
span = span_or_tx.start_child(op=key, start_timestamp=start_timestamp)
66+
_attach_tags(span, tags)
67+
span.finish(end_timestamp=timestamp)
68+
69+
def gauge(
70+
self,
71+
key: str,
72+
value: float,
73+
instance: str | None = None,
74+
tags: Tags | None = None,
75+
sample_rate: float = 1,
76+
unit: str | None = None,
77+
stacklevel: int = 0,
78+
) -> None:
79+
if self._keep_metric(sample_rate):
80+
_set_metric_on_span(key=key, value=value, op="gauge", tags=tags)
81+
82+
def distribution(
83+
self,
84+
key: str,
85+
value: float,
86+
instance: str | None = None,
87+
tags: Tags | None = None,
88+
sample_rate: float = 1,
89+
unit: str | None = None,
90+
stacklevel: int = 0,
91+
) -> None:
92+
if self._keep_metric(sample_rate):
93+
_set_metric_on_span(key=key, value=value, op="distribution", tags=tags)
94+
95+
def event(
96+
self,
97+
title: str,
98+
message: str,
99+
alert_type: str | None = None,
100+
aggregation_key: str | None = None,
101+
source_type_name: str | None = None,
102+
priority: str | None = None,
103+
instance: str | None = None,
104+
tags: Tags | None = None,
105+
stacklevel: int = 0,
106+
) -> None:
107+
pass

0 commit comments

Comments
 (0)