Skip to content

Commit 007058d

Browse files
ref(tracing): Use float for sample rand (#4677)
Closes #4270 --------- Co-authored-by: Anton Pirker <[email protected]>
1 parent dcefe38 commit 007058d

File tree

13 files changed

+49
-78
lines changed

13 files changed

+49
-78
lines changed

sentry_sdk/tracing.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from decimal import Decimal
21
import uuid
32
import warnings
43
from datetime import datetime, timedelta, timezone
@@ -1251,7 +1250,7 @@ def _set_initial_sampling_decision(self, sampling_context):
12511250
return
12521251

12531252
# Now we roll the dice.
1254-
self.sampled = self._sample_rand < Decimal.from_float(self.sample_rate)
1253+
self.sampled = self._sample_rand < self.sample_rate
12551254

12561255
if self.sampled:
12571256
logger.debug(

sentry_sdk/tracing_utils.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import sys
77
from collections.abc import Mapping
88
from datetime import timedelta
9-
from decimal import ROUND_DOWN, Decimal, DefaultContext, localcontext
109
from random import Random
1110
from urllib.parse import quote, unquote
1211
import uuid
@@ -502,7 +501,7 @@ def _fill_sample_rand(self):
502501
return
503502

504503
sample_rand = try_convert(
505-
Decimal, self.dynamic_sampling_context.get("sample_rand")
504+
float, self.dynamic_sampling_context.get("sample_rand")
506505
)
507506
if sample_rand is not None and 0 <= sample_rand < 1:
508507
# sample_rand is present and valid, so don't overwrite it
@@ -650,7 +649,7 @@ def populate_from_transaction(cls, transaction):
650649
options = client.options or {}
651650

652651
sentry_items["trace_id"] = transaction.trace_id
653-
sentry_items["sample_rand"] = str(transaction._sample_rand)
652+
sentry_items["sample_rand"] = f"{transaction._sample_rand:.6f}" # noqa: E231
654653

655654
if options.get("environment"):
656655
sentry_items["environment"] = options["environment"]
@@ -724,15 +723,15 @@ def strip_sentry_baggage(header):
724723
)
725724

726725
def _sample_rand(self):
727-
# type: () -> Optional[Decimal]
726+
# type: () -> Optional[float]
728727
"""Convenience method to get the sample_rand value from the sentry_items.
729728
730-
We validate the value and parse it as a Decimal before returning it. The value is considered
731-
valid if it is a Decimal in the range [0, 1).
729+
We validate the value and parse it as a float before returning it. The value is considered
730+
valid if it is a float in the range [0, 1).
732731
"""
733-
sample_rand = try_convert(Decimal, self.sentry_items.get("sample_rand"))
732+
sample_rand = try_convert(float, self.sentry_items.get("sample_rand"))
734733

735-
if sample_rand is not None and Decimal(0) <= sample_rand < Decimal(1):
734+
if sample_rand is not None and 0.0 <= sample_rand < 1.0:
736735
return sample_rand
737736

738737
return None
@@ -898,7 +897,7 @@ def _generate_sample_rand(
898897
*,
899898
interval=(0.0, 1.0), # type: tuple[float, float]
900899
):
901-
# type: (...) -> Decimal
900+
# type: (...) -> float
902901
"""Generate a sample_rand value from a trace ID.
903902
904903
The generated value will be pseudorandomly chosen from the provided
@@ -913,19 +912,16 @@ def _generate_sample_rand(
913912
raise ValueError("Invalid interval: lower must be less than upper")
914913

915914
rng = Random(trace_id)
916-
sample_rand = upper
917-
while sample_rand >= upper:
918-
sample_rand = rng.uniform(lower, upper)
919-
920-
# Round down to exactly six decimal-digit precision.
921-
# Setting the context is needed to avoid an InvalidOperation exception
922-
# in case the user has changed the default precision or set traps.
923-
with localcontext(DefaultContext) as ctx:
924-
ctx.prec = 6
925-
return Decimal(sample_rand).quantize(
926-
Decimal("0.000001"),
927-
rounding=ROUND_DOWN,
928-
)
915+
lower_scaled = int(lower * 1_000_000)
916+
upper_scaled = int(upper * 1_000_000)
917+
try:
918+
sample_rand_scaled = rng.randrange(lower_scaled, upper_scaled)
919+
except ValueError:
920+
# In some corner cases it might happen that the range is too small
921+
# In that case, just take the lower bound
922+
sample_rand_scaled = lower_scaled
923+
924+
return sample_rand_scaled / 1_000_000
929925

930926

931927
def _sample_rand_range(parent_sampled, sample_rate):

sentry_sdk/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,6 +1934,12 @@ def try_convert(convert_func, value):
19341934
given function. Return None if the conversion fails, i.e. if the function
19351935
raises an exception.
19361936
"""
1937+
try:
1938+
if isinstance(value, convert_func): # type: ignore
1939+
return value
1940+
except TypeError:
1941+
pass
1942+
19371943
try:
19381944
return convert_func(value)
19391945
except Exception:

tests/integrations/aiohttp/test_aiohttp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ async def handler(request):
618618

619619
raw_server = await aiohttp_raw_server(handler)
620620

621-
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.5):
621+
with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=500000):
622622
with start_transaction(
623623
name="/interactions/other-dogs/new-dog",
624624
op="greeting.sniff",

tests/integrations/celery/test_celery.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,8 +518,8 @@ def test_baggage_propagation(init_celery):
518518
def dummy_task(self, x, y):
519519
return _get_headers(self)
520520

521-
# patch random.uniform to return a predictable sample_rand value
522-
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.5):
521+
# patch random.randrange to return a predictable sample_rand value
522+
with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=500000):
523523
with start_transaction() as transaction:
524524
result = dummy_task.apply_async(
525525
args=(1, 0),

tests/integrations/httpx/test_httpx.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ def test_outgoing_trace_headers_append_to_baggage(
170170

171171
url = "http://example.com/"
172172

173-
# patch random.uniform to return a predictable sample_rand value
174-
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.5):
173+
# patch random.randrange to return a predictable sample_rand value
174+
with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=500000):
175175
with start_transaction(
176176
name="/interactions/other-dogs/new-dog",
177177
op="greeting.sniff",

tests/integrations/stdlib/test_httplib.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ def test_outgoing_trace_headers_head_sdk(sentry_init, monkeypatch):
236236
monkeypatch.setattr(HTTPSConnection, "send", mock_send)
237237

238238
sentry_init(traces_sample_rate=0.5, release="foo")
239-
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.25):
239+
with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=250000):
240240
transaction = Transaction.continue_from_headers({})
241241

242242
with start_transaction(transaction=transaction, name="Head SDK tx") as transaction:

tests/test_dsc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def my_traces_sampler(sampling_context):
175175
}
176176

177177
# We continue the incoming trace and start a new transaction
178-
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.125):
178+
with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=125000):
179179
transaction = sentry_sdk.continue_trace(incoming_http_headers)
180180
with sentry_sdk.start_transaction(transaction, name="foo"):
181181
pass

tests/test_monitor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def test_transaction_uses_downsampled_rate(
7373
assert monitor.downsample_factor == 1
7474

7575
# make sure we don't sample the transaction
76-
with mock.patch("sentry_sdk.tracing_utils.Random.uniform", return_value=0.75):
76+
with mock.patch("sentry_sdk.tracing_utils.Random.randrange", return_value=750000):
7777
with sentry_sdk.start_transaction(name="foobar") as transaction:
7878
assert transaction.sampled is False
7979
assert transaction.sample_rate == 0.5

tests/test_propagationcontext.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,13 @@ def test_sample_rand_filled(parent_sampled, sample_rate, expected_interval):
136136
else:
137137
sample_rate_str = ""
138138

139-
# for convenience, we'll just return the lower bound of the interval
140-
mock_uniform = mock.Mock(return_value=expected_interval[0])
139+
# for convenience, we'll just return the lower bound of the interval as an integer
140+
mock_randrange = mock.Mock(return_value=int(expected_interval[0] * 1000000))
141141

142142
def mock_random_class(seed):
143143
assert seed == "00000000000000000000000000000000", "seed should be the trace_id"
144144
rv = Mock()
145-
rv.uniform = mock_uniform
145+
rv.randrange = mock_randrange
146146
return rv
147147

148148
with mock.patch("sentry_sdk.tracing_utils.Random", mock_random_class):
@@ -158,17 +158,20 @@ def mock_random_class(seed):
158158
ctx.dynamic_sampling_context["sample_rand"]
159159
== f"{expected_interval[0]:.6f}" # noqa: E231
160160
)
161-
assert mock_uniform.call_count == 1
162-
assert mock_uniform.call_args[0] == expected_interval
161+
assert mock_randrange.call_count == 1
162+
assert mock_randrange.call_args[0] == (
163+
int(expected_interval[0] * 1000000),
164+
int(expected_interval[1] * 1000000),
165+
)
163166

164167

165168
def test_sample_rand_rounds_down():
166169
# Mock value that should round down to 0.999_999
167-
mock_uniform = mock.Mock(return_value=0.999_999_9)
170+
mock_randrange = mock.Mock(return_value=999999)
168171

169172
def mock_random_class(_):
170173
rv = Mock()
171-
rv.uniform = mock_uniform
174+
rv.randrange = mock_randrange
172175
return rv
173176

174177
with mock.patch("sentry_sdk.tracing_utils.Random", mock_random_class):

0 commit comments

Comments
 (0)