c43e1f8260
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 14s
CI / quality (pull_request) Successful in 19s
CI / build (pull_request) Successful in 14s
CI / typecheck (pull_request) Successful in 31s
CI / security (pull_request) Successful in 32s
CI / unit_tests (pull_request) Successful in 1m52s
CI / docker (pull_request) Successful in 42s
CI / integration_tests (pull_request) Successful in 2m59s
CI / coverage (pull_request) Successful in 4m14s
CI / lint (push) Successful in 15s
CI / quality (push) Successful in 16s
CI / security (push) Successful in 30s
CI / typecheck (push) Successful in 1m3s
CI / build (push) Successful in 18s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m27s
CI / integration_tests (push) Successful in 3m0s
CI / docker (push) Successful in 50s
CI / coverage (push) Successful in 5m8s
CI / benchmark-publish (push) Has been cancelled
CI / benchmark-regression (pull_request) Successful in 29m24s
Add SubplanService for building child plans from DecisionService spawn entries and SubplanConfig. The service validates resource scopes, merge strategies, and max_parallel bounds before spawning. Key additions: - SubplanService: Orchestrates child plan creation from spawn entries - SpawnMetadata: Persisted metadata (spawn_decision_id, parent/root plan IDs, execution mode) for status output - Spawn validation: Checks resource scopes, merge strategy, and parallelism bounds before spawning - Documentation: subplan_service.md with spawn workflow and lifecycle ISSUES CLOSED: #197
149 lines
4.6 KiB
Python
149 lines
4.6 KiB
Python
"""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())
|