Files
computer-playbook/tests/unit/python/plugins/lookup/test_playwright_workers.py
T
kevinveenbirkenbachandClaude Opus 5 a112364e17 perf(playwright): let the worker count answer to the transport
The sidecar is the dominant cost of an onion round and almost free without it.
Measured on one Nextcloud variant at equal mode and filesystem, differing only
in the onion axis, sync pass:

    test-e2e-playwright   4817s over Tor    911s on clearnet   +5.3x
    sys-svc-compose       2294s             2174s              +120s
    everything else                                            < +25s

The whole Tor surcharge sits in one role. Tor burns no browser CPU, so a 5.3x
wall clock against identical specs on identical cores is circuit latency: the
phase waits rather than computes. The CPU divisor that sizes the clearnet count
does not describe it, and on a 4-core runner it yields exactly one worker,
which leaves the CI cap of 2 unreachable.

compute_workers takes an onion flag and, when set, multiplies the count and
caps it separately. The CI ceiling is overridden rather than respected: it
describes CPU contention, which Tor does not create. ram_workers keeps its
veto, because circuits are cheap and browsers are not.

The lookup reads the transport itself, from the same source the role derives
its own flag from, lookup(tls, application_id, url.base) tested for .onion,
following the _swarm_prefix idiom in plugins/lookup/image.py: read from
variables, render through the templar, fall back to the safe answer. One truth
about the onion state, and a caller asking for a worker count cannot forget to
pass it.

Factor 2 and cap 4 rather than more: each worker holds its own circuits and the
Tor daemon is the next bound, which no measurement here reaches. Expect the
sidecar near 2.6ks per pass instead of 4.8ks, roughly 4.4ks per onion job
against a 20.4ks budget. It does not help a clearnet round, where the sidecar
costs 911s to begin with.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 13:11:32 +02:00

61 lines
2.0 KiB
Python

import unittest
from plugins.lookup.playwright_workers import LookupModule, compute_workers
class TestComputeWorkers(unittest.TestCase):
def test_local_20c_64g(self):
self.assertEqual(compute_workers(20, 64.0, False), 5)
def test_local_12c_31g(self):
self.assertEqual(compute_workers(12, 31.0, False), 3)
def test_hard_cap_binds(self):
self.assertEqual(compute_workers(64, 256.0, False), 6)
def test_ram_binds(self):
self.assertEqual(compute_workers(20, 3.0, False), 1)
def test_ci_cap_binds(self):
self.assertEqual(compute_workers(16, 64.0, True), 2)
def test_ci_small_runner(self):
self.assertEqual(compute_workers(2, 8.0, True), 1)
def test_floor_is_one(self):
self.assertEqual(compute_workers(1, 1.0, False), 1)
def test_kwargs_override(self):
self.assertEqual(
compute_workers(20, 64.0, False, cpu_divisor=2, hard_cap=100), 10
)
def test_onion_lifts_the_ci_runner_off_one_worker(self):
"""The CI ceiling describes CPU contention, which Tor does not create.
The 4-core runner yields one worker because a quarter of four cores is
one. Over Tor the phase waits on circuits instead, so the count is
raised rather than derived from cores.
"""
self.assertEqual(compute_workers(4, 16.0, True), 1)
self.assertEqual(compute_workers(4, 16.0, True, onion=True), 2)
def test_onion_still_respects_memory(self):
"""Circuits are cheap, browsers are not: RAM keeps its veto."""
self.assertEqual(compute_workers(20, 3.0, False, onion=True), 1)
def test_onion_cap_binds_on_a_large_host(self):
self.assertEqual(compute_workers(64, 256.0, False, onion=True), 4)
class TestLookup(unittest.TestCase):
def test_returns_single_int_ge_1(self):
out = LookupModule().run([], variables={})
self.assertEqual(len(out), 1)
self.assertIsInstance(out[0], int)
self.assertGreaterEqual(out[0], 1)
if __name__ == "__main__":
unittest.main()