Skip to content

Commit a688ca6

Browse files
committed
feat(tracemetrics): Add sample_rate to metrics api calls
This allows for a sample_rate (0, 1.0] to be sent on a per metric basis.
1 parent faa327c commit a688ca6

File tree

2 files changed

+94
-5
lines changed

2 files changed

+94
-5
lines changed

sentry_sdk/metrics.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
2-
NOTE: This file contains experimental code that may be changed or removed at any
3-
time without prior notice.
2+
NOTE: This file contains experimental code that may be changed or removed at
3+
any time without prior notice.
44
"""
55

66
import time
@@ -19,6 +19,7 @@ def _capture_metric(
1919
value, # type: float
2020
unit=None, # type: Optional[str]
2121
attributes=None, # type: Optional[dict[str, Any]]
22+
sample_rate=None, # type: Optional[float]
2223
):
2324
# type: (...) -> None
2425
client = sentry_sdk.get_client()
@@ -37,6 +38,19 @@ def _capture_metric(
3738
else safe_repr(v)
3839
)
3940

41+
if sample_rate is not None:
42+
if sample_rate <= 0.0 or sample_rate > 1.0:
43+
if client.transport is not None:
44+
client.transport.record_lost_event(
45+
"invalid_sample_rate",
46+
data_category="trace_metric",
47+
quantity=1,
48+
)
49+
return
50+
51+
if sample_rate != 1.0:
52+
attrs["sentry.client_sample_rate"] = sample_rate
53+
4054
metric = {
4155
"timestamp": time.time(),
4256
"trace_id": None,
@@ -56,26 +70,29 @@ def count(
5670
value, # type: float
5771
unit=None, # type: Optional[str]
5872
attributes=None, # type: Optional[dict[str, Any]]
73+
sample_rate=None, # type: Optional[float]
5974
):
6075
# type: (...) -> None
61-
_capture_metric(name, "counter", value, unit, attributes)
76+
_capture_metric(name, "counter", value, unit, attributes, sample_rate)
6277

6378

6479
def gauge(
6580
name, # type: str
6681
value, # type: float
6782
unit=None, # type: Optional[str]
6883
attributes=None, # type: Optional[dict[str, Any]]
84+
sample_rate=None, # type: Optional[float]
6985
):
7086
# type: (...) -> None
71-
_capture_metric(name, "gauge", value, unit, attributes)
87+
_capture_metric(name, "gauge", value, unit, attributes, sample_rate)
7288

7389

7490
def distribution(
7591
name, # type: str
7692
value, # type: float
7793
unit=None, # type: Optional[str]
7894
attributes=None, # type: Optional[dict[str, Any]]
95+
sample_rate=None, # type: Optional[float]
7996
):
8097
# type: (...) -> None
81-
_capture_metric(name, "distribution", value, unit, attributes)
98+
_capture_metric(name, "distribution", value, unit, attributes, sample_rate)

tests/test_metrics.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,3 +267,75 @@ def record_lost_event(reason, data_category, quantity):
267267
assert len(lost_event_calls) == 5
268268
for lost_event_call in lost_event_calls:
269269
assert lost_event_call == ("queue_overflow", "trace_metric", 1)
270+
271+
272+
def test_metrics_sample_rate_basic(sentry_init, capture_envelopes):
273+
sentry_init()
274+
envelopes = capture_envelopes()
275+
276+
sentry_sdk.metrics.count("test.counter", 1, sample_rate=0.5)
277+
sentry_sdk.metrics.gauge("test.gauge", 42, sample_rate=0.8)
278+
sentry_sdk.metrics.distribution("test.distribution", 200, sample_rate=1.0)
279+
280+
get_client().flush()
281+
metrics = envelopes_to_metrics(envelopes)
282+
283+
assert len(metrics) == 3
284+
285+
assert metrics[0]["name"] == "test.counter"
286+
assert metrics[0]["attributes"]["sentry.client_sample_rate"] == 0.5
287+
288+
assert metrics[1]["name"] == "test.gauge"
289+
assert metrics[1]["attributes"]["sentry.client_sample_rate"] == 0.8
290+
291+
assert metrics[2]["name"] == "test.distribution"
292+
assert "sentry.client_sample_rate" not in metrics[2]["attributes"]
293+
294+
295+
def test_metrics_sample_rate_normalization(sentry_init, capture_envelopes, monkeypatch):
296+
sentry_init()
297+
envelopes = capture_envelopes()
298+
client = sentry_sdk.get_client()
299+
300+
lost_event_calls = []
301+
302+
def record_lost_event(reason, data_category, quantity):
303+
lost_event_calls.append((reason, data_category, quantity))
304+
305+
monkeypatch.setattr(client.transport, "record_lost_event", record_lost_event)
306+
307+
sentry_sdk.metrics.count("test.counter1", 1, sample_rate=0.0) # <= 0
308+
sentry_sdk.metrics.count("test.counter2", 1, sample_rate=-0.5) # < 0
309+
sentry_sdk.metrics.count("test.counter3", 1, sample_rate=0.5) # > 0 but < 1.0
310+
sentry_sdk.metrics.count("test.counter4", 1, sample_rate=1.0) # = 1.0
311+
sentry_sdk.metrics.count("test.counter4", 1, sample_rate=1.5) # > 1.0
312+
313+
client.flush()
314+
metrics = envelopes_to_metrics(envelopes)
315+
316+
assert len(metrics) == 2
317+
318+
assert metrics[0]["attributes"]["sentry.client_sample_rate"] == 0.5
319+
assert (
320+
"sentry.client_sample_rate" not in metrics[1]["attributes"]
321+
) # 1.0 does not need a sample rate, it's implied to be 1.0
322+
323+
assert len(lost_event_calls) == 3
324+
assert lost_event_calls[0] == ("invalid_sample_rate", "trace_metric", 1)
325+
assert lost_event_calls[1] == ("invalid_sample_rate", "trace_metric", 1)
326+
assert lost_event_calls[2] == ("invalid_sample_rate", "trace_metric", 1)
327+
328+
329+
def test_metrics_no_sample_rate(sentry_init, capture_envelopes):
330+
sentry_init()
331+
envelopes = capture_envelopes()
332+
333+
sentry_sdk.metrics.count("test.counter", 1)
334+
335+
get_client().flush()
336+
metrics = envelopes_to_metrics(envelopes)
337+
338+
assert len(metrics) == 1
339+
340+
# Should not have sample_rate attribute when not provided
341+
assert "sentry.client_sample_rate" not in metrics[0]["attributes"]

0 commit comments

Comments
 (0)