Skip to content

Commit a3fa088

Browse files
committed
ci(tests): Split monster test files dominating shard balance
test_organization_agent_token.py held 1680 collected tests (runtime-generated matrix) and test_transactions_rebalancing.py 679 (module-global parametrize), each landing whole or striped across shards and wrecking balance. Split into per-theme files with identical collected totals (1680 = 30+15+1635, 679 = 504+168+6+1, verified by collect-only). Shared matrix/param infrastructure is imported from the kept originals, following existing cross-test import precedent. Seed durations for the new files by splitting the measured per-file seconds proportionally to collected tests, so the next full run plans balanced immediately.
1 parent 356f644 commit a3fa088

8 files changed

Lines changed: 2380 additions & 2272 deletions

‎.github/workflows/scripts/backend-test-durations.json‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,10 @@
459459
"tests/sentry/discover/translation/test_mep_to_eap.py": 1.0,
460460
"tests/sentry/dynamic_sampling/models/test_base.py": 0.0,
461461
"tests/sentry/dynamic_sampling/models/test_projects_rebalancing.py": 0.1,
462-
"tests/sentry/dynamic_sampling/models/test_transactions_rebalancing.py": 9.3,
462+
"tests/sentry/dynamic_sampling/models/test_transactions_rebalancing.py": 6.9,
463+
"tests/sentry/dynamic_sampling/models/test_transactions_rebalancing_explicit.py": 2.3,
464+
"tests/sentry/dynamic_sampling/models/test_transactions_rebalancing_floor.py": 0.1,
465+
"tests/sentry/dynamic_sampling/models/test_transactions_rebalancing_validation.py": 0.0,
463466
"tests/sentry/dynamic_sampling/per_org/test_cache.py": 4.6,
464467
"tests/sentry/dynamic_sampling/per_org/test_calculations.py": 5.0,
465468
"tests/sentry/dynamic_sampling/per_org/test_configuration.py": 5.5,
@@ -1766,7 +1769,9 @@
17661769
"tests/sentry/seer/endpoints/test_group_autofix_setup_check.py": 8.9,
17671770
"tests/sentry/seer/endpoints/test_issue_view_title_generate.py": 1.3,
17681771
"tests/sentry/seer/endpoints/test_organization_agent_approve.py": 2.9,
1769-
"tests/sentry/seer/endpoints/test_organization_agent_token.py": 794.0,
1772+
"tests/sentry/seer/endpoints/test_organization_agent_token.py": 14.2,
1773+
"tests/sentry/seer/endpoints/test_organization_agent_token_matrix.py": 772.7,
1774+
"tests/sentry/seer/endpoints/test_organization_agent_token_superuser.py": 7.1,
17701775
"tests/sentry/seer/endpoints/test_organization_events_anomalies.py": 3.1,
17711776
"tests/sentry/seer/endpoints/test_organization_seer_agent_chat.py": 9.5,
17721777
"tests/sentry/seer/endpoints/test_organization_seer_agent_update.py": 3.1,
Lines changed: 1 addition & 273 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
from collections.abc import Mapping, Sequence
1+
from collections.abc import Mapping
22

33
import pytest
44

5-
from sentry.dynamic_sampling.models.base import InvalidModelInputError
65
from sentry.dynamic_sampling.models.common import RebalancedItem, sum_classes_counts
76
from sentry.dynamic_sampling.models.transactions_rebalancing import (
87
TransactionsRebalancingInput,
@@ -85,274 +84,3 @@ def test_maintains_overall_sample_rate(
8584
new_sampled_transactions = get_num_sampled_elements(transactions, trans_dict, global_rate)
8685

8786
assert new_sampled_transactions == pytest.approx(old_sampled_transactions)
88-
89-
90-
@pytest.mark.parametrize("sample_rate", sample_rates)
91-
@pytest.mark.parametrize("transactions", test_resample_cases)
92-
@pytest.mark.parametrize("idx_low,idx_high", excluded_transactions)
93-
def test_explicit_elements_ideal_rate(
94-
transactions_rebalancing_model, sample_rate, transactions, idx_low, idx_high
95-
):
96-
"""
97-
Tests that the explicitly specified elements are sampled at their ideal rate.
98-
99-
This test is performed at intensity=1.0
100-
Ideal sample rate means that the resulting number of sampled elements is the minimum between:
101-
* all transactions in the class (sampled at rate 1.0)
102-
* the budget per transaction
103-
"""
104-
explict_transactions = transactions[idx_low:idx_high]
105-
total = sum_classes_counts(transactions)
106-
total_classes = len(transactions)
107-
108-
trans, global_rate = transactions_rebalancing_model.run(
109-
TransactionsRebalancingInput(
110-
classes=explict_transactions,
111-
sample_rate=sample_rate,
112-
total_num_classes=total_classes,
113-
total=total,
114-
intensity=1,
115-
),
116-
)
117-
118-
ideal_number_of_elements_per_class = total * sample_rate / total_classes
119-
120-
trans_dict = {t.id: t.new_sample_rate for t in trans}
121-
122-
for transaction in explict_transactions:
123-
count = transaction.count
124-
actual_rate = trans_dict[transaction.id]
125-
126-
if ideal_number_of_elements_per_class > count:
127-
assert actual_rate == 1.0 # tiny transactions not sampled
128-
else:
129-
assert (
130-
actual_rate * count == pytest.approx(ideal_number_of_elements_per_class)
131-
or actual_rate * count >= ideal_number_of_elements_per_class
132-
)
133-
134-
135-
def _run(
136-
model: TransactionsRebalancingModel,
137-
*,
138-
classes: list[RebalancedItem],
139-
sample_rate: float,
140-
total_num_classes: int,
141-
total: float,
142-
intensity: float = 1.0,
143-
min_sample_rate: float = 0.0,
144-
) -> tuple[list[RebalancedItem], float]:
145-
return model.run(
146-
TransactionsRebalancingInput(
147-
classes=classes,
148-
sample_rate=sample_rate,
149-
total_num_classes=total_num_classes,
150-
total=total,
151-
intensity=intensity,
152-
min_sample_rate=min_sample_rate,
153-
)
154-
)
155-
156-
157-
def _overall_kept(
158-
explicit_rates: Sequence[RebalancedItem], implicit_rate: float, total: float
159-
) -> float:
160-
"""Expected number of kept samples across explicit classes and the implicit (tail) classes."""
161-
explicit_kept = sum(item.new_sample_rate * item.count for item in explicit_rates)
162-
total_implicit = total - sum_classes_counts(list(explicit_rates))
163-
return explicit_kept + implicit_rate * total_implicit
164-
165-
166-
def test_high_class_count_crushes_dominant_transaction_without_floor(
167-
transactions_rebalancing_model,
168-
) -> None:
169-
"""
170-
Reproduces TET-2535: one transaction carrying half the project volume while competing against a
171-
very large number of low-volume classes. With no floor the per-class ideal collapses, the
172-
dominant transaction is driven to a ~1e-6 rate (a ~1,000,000x extrapolation factor) and is
173-
expected to keep only ~1 raw sample - the spiky "lottery" behaviour we want to remove.
174-
"""
175-
big = RebalancedItem(id="big", count=1_000_000)
176-
177-
explicit_rates, implicit_rate = _run(
178-
transactions_rebalancing_model,
179-
classes=[big],
180-
sample_rate=0.05,
181-
total_num_classes=100_000,
182-
total=2_000_000,
183-
min_sample_rate=0.0,
184-
)
185-
186-
(big_rate,) = explicit_rates
187-
assert big_rate.new_sample_rate == pytest.approx(1e-6)
188-
# the dominant transaction expects to keep ~1 raw sample, yet the tail is sampled ~100,000x denser
189-
assert big_rate.new_sample_rate * big_rate.count == pytest.approx(1.0)
190-
assert implicit_rate == pytest.approx(0.099999)
191-
assert implicit_rate / big_rate.new_sample_rate == pytest.approx(99999.0)
192-
193-
194-
def test_min_sample_rate_floors_dominant_transaction(transactions_rebalancing_model) -> None:
195-
"""
196-
Same scenario as above, but with the floor enabled: the dominant transaction's rate is floored
197-
so its extrapolation factor is capped at 1 / min_sample_rate, the cost is reclaimed from the
198-
implicit tail, and the overall sampling rate is still maintained exactly.
199-
"""
200-
big = RebalancedItem(id="big", count=1_000_000)
201-
202-
explicit_rates, implicit_rate = _run(
203-
transactions_rebalancing_model,
204-
classes=[big],
205-
sample_rate=0.05,
206-
total_num_classes=100_000,
207-
total=2_000_000,
208-
min_sample_rate=0.001,
209-
)
210-
211-
(big_rate,) = explicit_rates
212-
# rate floored to 0.001 => extrapolation factor capped at 1000 (was 1,000,000)
213-
assert big_rate.new_sample_rate == pytest.approx(0.001)
214-
# expected kept samples jump from ~1 to ~1000, killing the shot noise
215-
assert big_rate.new_sample_rate * big_rate.count == pytest.approx(1000.0)
216-
# the budget is reclaimed by lowering the tail rate, not by exceeding the overall budget
217-
assert implicit_rate == pytest.approx(0.099)
218-
assert _overall_kept(explicit_rates, implicit_rate, total=2_000_000) == pytest.approx(
219-
0.05 * 2_000_000
220-
)
221-
222-
223-
def test_min_sample_rate_with_many_explicit_transactions(transactions_rebalancing_model) -> None:
224-
"""
225-
Production-shaped case: 30 high-volume explicit transactions (60% of volume) competing with a
226-
~50k-class tail. Without the floor every head transaction collapses to a 5e-5 rate (20,000x
227-
factor); with the floor they are lifted to the floor and the tail rate barely moves.
228-
"""
229-
classes = [RebalancedItem(id=f"big{i}", count=20_000) for i in range(30)]
230-
231-
no_floor_rates, no_floor_implicit = _run(
232-
transactions_rebalancing_model,
233-
classes=classes,
234-
sample_rate=0.05,
235-
total_num_classes=50_000,
236-
total=1_000_000,
237-
min_sample_rate=0.0,
238-
)
239-
assert all(item.new_sample_rate == pytest.approx(5e-5) for item in no_floor_rates)
240-
241-
floored_rates, floored_implicit = _run(
242-
transactions_rebalancing_model,
243-
classes=classes,
244-
sample_rate=0.05,
245-
total_num_classes=50_000,
246-
total=1_000_000,
247-
min_sample_rate=0.001,
248-
)
249-
250-
assert all(item.new_sample_rate == pytest.approx(0.001) for item in floored_rates)
251-
# reclaimed from the tail; the implicit rate drops but stays well above zero
252-
assert floored_implicit < no_floor_implicit
253-
assert floored_implicit == pytest.approx(0.1235)
254-
assert _overall_kept(floored_rates, floored_implicit, total=1_000_000) == pytest.approx(
255-
0.05 * 1_000_000
256-
)
257-
258-
259-
def test_min_sample_rate_clamped_to_overall_rate(transactions_rebalancing_model) -> None:
260-
"""
261-
For a project whose overall rate is already below the configured floor, the floor is clamped to
262-
the overall rate, so a class is never sampled above the project rate and the budget cannot be
263-
overshot.
264-
"""
265-
big = RebalancedItem(id="big", count=1_000_000)
266-
267-
explicit_rates, implicit_rate = _run(
268-
transactions_rebalancing_model,
269-
classes=[big],
270-
sample_rate=0.0005,
271-
total_num_classes=100_000,
272-
total=2_000_000,
273-
min_sample_rate=0.001,
274-
)
275-
276-
(big_rate,) = explicit_rates
277-
assert big_rate.new_sample_rate == pytest.approx(0.0005)
278-
assert big_rate.new_sample_rate <= 0.0005
279-
assert _overall_kept(explicit_rates, implicit_rate, total=2_000_000) == pytest.approx(
280-
0.0005 * 2_000_000
281-
)
282-
283-
284-
def test_min_sample_rate_inert_when_rates_healthy(transactions_rebalancing_model) -> None:
285-
"""When the natural rates already sit above the floor, enabling it changes nothing."""
286-
classes = [RebalancedItem(id=f"big{i}", count=1000) for i in range(3)]
287-
288-
base_rates, base_implicit = _run(
289-
transactions_rebalancing_model,
290-
classes=classes,
291-
sample_rate=0.5,
292-
total_num_classes=20,
293-
total=10_000,
294-
min_sample_rate=0.0,
295-
)
296-
floored_rates, floored_implicit = _run(
297-
transactions_rebalancing_model,
298-
classes=classes,
299-
sample_rate=0.5,
300-
total_num_classes=20,
301-
total=10_000,
302-
min_sample_rate=1e-6,
303-
)
304-
305-
assert {item.id: item.new_sample_rate for item in floored_rates} == {
306-
item.id: item.new_sample_rate for item in base_rates
307-
}
308-
assert floored_implicit == pytest.approx(base_implicit)
309-
310-
311-
def test_min_sample_rate_out_of_range_is_invalid(transactions_rebalancing_model) -> None:
312-
with pytest.raises(InvalidModelInputError):
313-
_run(
314-
transactions_rebalancing_model,
315-
classes=[RebalancedItem(id="big", count=1000)],
316-
sample_rate=0.5,
317-
total_num_classes=10,
318-
total=10_000,
319-
min_sample_rate=1.5,
320-
)
321-
322-
323-
def test_total_num_classes_mismatch(transactions_rebalancing_model) -> None:
324-
"""
325-
Simple test case that checks that the model is resilient to cases where the
326-
reported total number of classes is less than the number of passed classes
327-
"""
328-
sample_rate = 0.9
329-
transactions = create_transaction_counts(big=3, med=4, small=2)
330-
explict_transactions = transactions[0:None]
331-
total = sum_classes_counts(transactions)
332-
total_classes = len(transactions)
333-
334-
trans, global_rate = transactions_rebalancing_model.run(
335-
TransactionsRebalancingInput(
336-
classes=explict_transactions,
337-
sample_rate=sample_rate,
338-
total_num_classes=total_classes - 1,
339-
total=total,
340-
intensity=1,
341-
),
342-
)
343-
344-
ideal_number_of_elements_per_class = total * sample_rate / total_classes
345-
346-
trans_dict = {t.id: t.new_sample_rate for t in trans}
347-
348-
for transaction in explict_transactions:
349-
count = transaction.count
350-
actual_rate = trans_dict[transaction.id]
351-
352-
if ideal_number_of_elements_per_class > count:
353-
assert actual_rate == 1.0 # tiny transactions not sampled
354-
else:
355-
assert (
356-
actual_rate * count == pytest.approx(ideal_number_of_elements_per_class)
357-
or actual_rate * count >= ideal_number_of_elements_per_class
358-
)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import pytest
2+
3+
from sentry.dynamic_sampling.models.common import sum_classes_counts
4+
from sentry.dynamic_sampling.models.transactions_rebalancing import (
5+
TransactionsRebalancingInput,
6+
TransactionsRebalancingModel,
7+
)
8+
9+
10+
@pytest.fixture
11+
def transactions_rebalancing_model():
12+
return TransactionsRebalancingModel()
13+
14+
15+
from tests.sentry.dynamic_sampling.models.test_transactions_rebalancing import (
16+
excluded_transactions,
17+
sample_rates,
18+
test_resample_cases,
19+
)
20+
21+
22+
@pytest.mark.parametrize("sample_rate", sample_rates)
23+
@pytest.mark.parametrize("transactions", test_resample_cases)
24+
@pytest.mark.parametrize("idx_low,idx_high", excluded_transactions)
25+
def test_explicit_elements_ideal_rate(
26+
transactions_rebalancing_model, sample_rate, transactions, idx_low, idx_high
27+
):
28+
"""
29+
Tests that the explicitly specified elements are sampled at their ideal rate.
30+
31+
This test is performed at intensity=1.0
32+
Ideal sample rate means that the resulting number of sampled elements is the minimum between:
33+
* all transactions in the class (sampled at rate 1.0)
34+
* the budget per transaction
35+
"""
36+
explict_transactions = transactions[idx_low:idx_high]
37+
total = sum_classes_counts(transactions)
38+
total_classes = len(transactions)
39+
40+
trans, global_rate = transactions_rebalancing_model.run(
41+
TransactionsRebalancingInput(
42+
classes=explict_transactions,
43+
sample_rate=sample_rate,
44+
total_num_classes=total_classes,
45+
total=total,
46+
intensity=1,
47+
),
48+
)
49+
50+
ideal_number_of_elements_per_class = total * sample_rate / total_classes
51+
52+
trans_dict = {t.id: t.new_sample_rate for t in trans}
53+
54+
for transaction in explict_transactions:
55+
count = transaction.count
56+
actual_rate = trans_dict[transaction.id]
57+
58+
if ideal_number_of_elements_per_class > count:
59+
assert actual_rate == 1.0 # tiny transactions not sampled
60+
else:
61+
assert (
62+
actual_rate * count == pytest.approx(ideal_number_of_elements_per_class)
63+
or actual_rate * count >= ideal_number_of_elements_per_class
64+
)

0 commit comments

Comments
 (0)