mirror of
https://github.com/esphome/esphome.git
synced 2026-10-10 05:23:07 +00:00
[ci] Keep bus group partners on the same runner (#20431)
This commit is contained in:
@@ -262,8 +262,9 @@ def balance_batches(items: list[_BatchItem], target_seconds: int) -> list[list[s
|
||||
"""Spread items over enough runners to stay near target_seconds each.
|
||||
|
||||
The runner count comes from the total estimate with every grouped build
|
||||
counted once. Heaviest items go first, each to the runner that ends up
|
||||
lightest, so grouped components follow the builds they can join.
|
||||
counted once. Heaviest items go first, each to the runner it adds the
|
||||
fewest seconds to among those ending near the lightest, so grouped
|
||||
components follow the builds they can join.
|
||||
"""
|
||||
if not items:
|
||||
return []
|
||||
@@ -274,10 +275,23 @@ def balance_batches(items: list[_BatchItem], target_seconds: int) -> list[list[s
|
||||
total = whole.seconds
|
||||
count = min(len(items), max(1, math.ceil(total / target_seconds)))
|
||||
batches = [_Batch() for _ in range(count)]
|
||||
# Among the runners ending within slack of the lightest, the one the item
|
||||
# adds the fewest seconds to wins, so a component lands with the bus
|
||||
# group partners it shares builds with; placed away from them it builds
|
||||
# alone on every platform they only share there.
|
||||
slack = target_seconds // 4
|
||||
for item in sorted(items, key=lambda i: (-i.standalone_seconds(), i.component)):
|
||||
min(batches, key=lambda b: (b.seconds + b.added_seconds(item), b.seconds)).add(
|
||||
item
|
||||
costs = [(batch, batch.added_seconds(item)) for batch in batches]
|
||||
ceiling = min(batch.seconds + added for batch, added in costs) + slack
|
||||
batch, _ = min(
|
||||
(
|
||||
(batch, added)
|
||||
for batch, added in costs
|
||||
if batch.seconds + added <= ceiling
|
||||
),
|
||||
key=lambda cost: (cost[1], cost[0].seconds + cost[1]),
|
||||
)
|
||||
batch.add(item)
|
||||
return [batch.components for batch in batches if batch.components]
|
||||
|
||||
|
||||
|
||||
@@ -249,6 +249,24 @@ def test_make_item_reads_tests_dir(tmp_path: Path) -> None:
|
||||
assert item.own_seconds == 55
|
||||
|
||||
|
||||
def test_balance_batches_keeps_partners_together() -> None:
|
||||
"""Two components sharing a build land on one runner even when the
|
||||
lightest runner is the other one."""
|
||||
share = {
|
||||
("i2c", "esp32-idf"): split_components_for_ci._GroupedShare(55, frozenset()),
|
||||
("i2c", "esp8266-ard"): split_components_for_ci._GroupedShare(30, frozenset()),
|
||||
}
|
||||
items = [
|
||||
split_components_for_ci._BatchItem("partner_a", 0, dict(share)),
|
||||
split_components_for_ci._BatchItem("partner_b", 0, dict(share)),
|
||||
split_components_for_ci._BatchItem("alone", 80),
|
||||
]
|
||||
|
||||
batches = split_components_for_ci.balance_batches(items, target_seconds=120)
|
||||
|
||||
assert sorted(batches) == [["alone"], ["partner_a", "partner_b"]]
|
||||
|
||||
|
||||
def test_balance_batches_empty() -> None:
|
||||
"""No components means no runners."""
|
||||
assert split_components_for_ci.balance_batches([], 600) == []
|
||||
|
||||
Reference in New Issue
Block a user