"""Airspeed Velocity benchmarks for subplan spawn service throughput. Measures spawn creation, validation, and entry building overhead for SubplanService across varying numbers of spawn entries. """ from __future__ import annotations from unittest.mock import MagicMock from cleveragents.application.services.subplan_service import ( SpawnEntry, SubplanService, ) from cleveragents.domain.models.core.decision import ( ContextSnapshot, Decision, DecisionType, ResourceRef, ) from cleveragents.domain.models.core.plan import ( ExecutionMode, NamespacedName, Plan, PlanIdentity, SubplanConfig, ) _PLAN_ID = "01HGZ6FE0AQDYTR4BXVQZ6PN00" def _mock_decision_service() -> MagicMock: svc: MagicMock = MagicMock() svc.list_by_type = MagicMock(return_value=[]) return svc def _make_decision( decision_id: str, sequence: int = 0, resource_ids: list[str] | None = None, ) -> Decision: resources: list[ResourceRef] = [] if resource_ids: resources = [ResourceRef(resource_id=rid) for rid in resource_ids] return Decision( decision_id=decision_id, plan_id=_PLAN_ID, decision_type=DecisionType.SUBPLAN_SPAWN, sequence_number=sequence, question="Spawn child plan?", chosen_option="local/sub-action", context_snapshot=ContextSnapshot(relevant_resources=resources), ) def _make_plan() -> Plan: return Plan( identity=PlanIdentity(plan_id=_PLAN_ID), namespaced_name=NamespacedName(namespace="local", name="bench-plan"), description="Benchmark plan", action_name="local/bench-action", ) class SubplanSpawnThroughputSuite: """Benchmark SubplanService spawn throughput.""" def setup(self) -> None: """Prepare fixtures for spawn benchmarks.""" self.service = SubplanService( decision_service=_mock_decision_service(), ) self.plan = _make_plan() self.seq_config = SubplanConfig( execution_mode=ExecutionMode.SEQUENTIAL, ) self.par_config = SubplanConfig( execution_mode=ExecutionMode.PARALLEL, max_parallel=50, ) # Build entries for varying sizes self.entries_1 = self._build_entries(1) self.entries_5 = self._build_entries(5) self.entries_20 = self._build_entries(20) def _build_entries(self, count: int) -> list[SpawnEntry]: entries: list[SpawnEntry] = [] for i in range(count): did = f"01HGZ6FE0AQDYTR4BXVQZ6D{i:02d}0" dec = _make_decision(did, sequence=i) entries.append(SpawnEntry(decision=dec, action_name=f"local/sub-{i}")) return entries def time_spawn_single_sequential(self) -> None: """Time spawning a single child plan in sequential mode.""" self.service.spawn( parent_plan=self.plan, config=self.seq_config, spawn_entries=self.entries_1, ) def time_spawn_5_sequential(self) -> None: """Time spawning 5 child plans in sequential mode.""" self.service.spawn( parent_plan=self.plan, config=self.seq_config, spawn_entries=self.entries_5, ) def time_spawn_20_parallel(self) -> None: """Time spawning 20 child plans in parallel mode.""" self.service.spawn( parent_plan=self.plan, config=self.par_config, spawn_entries=self.entries_20, ) def time_validate_5_entries(self) -> None: """Time validating 5 spawn entries.""" self.service.validate_spawn( config=self.seq_config, spawn_entries=self.entries_5, ) def time_validate_20_entries_with_resources(self) -> None: """Time validating 20 entries with resource scope check.""" self.service.validate_spawn( config=self.par_config, spawn_entries=self.entries_20, available_resources=frozenset(f"res-{i}" for i in range(100)), ) def time_build_entries_from_decisions(self) -> None: """Time building spawn entries from 5 decisions.""" decisions = [ _make_decision( f"01HGZ6FE0AQDYTR4BXVQZ6D{i:02d}0", sequence=i, resource_ids=[f"01HGZ6FE0AQDYTR4BXVQZ6R{i:02d}0"], ) for i in range(5) ] self.service.build_spawn_entries(decisions) def time_service_construction(self) -> None: """Time constructing SubplanService.""" SubplanService(decision_service=_mock_decision_service())