"""ASV benchmarks for devcontainer lifecycle activation latency. Measures the performance of: - ContainerLifecycleState transition validation - Lifecycle tracker construction and transition - Lazy activation with mock runner (no real subprocess) - JSON output parsing from devcontainer up - Lifecycle registry operations (get/set/list) Based on issue #514: Devcontainer lifecycle management. """ from __future__ import annotations import json import sys from pathlib import Path from typing import ClassVar try: from cleveragents.domain.models.core.container_lifecycle import ( ContainerLifecycleState, ContainerLifecycleTracker, transition_state, validate_transition, ) from cleveragents.resource.handlers.devcontainer import ( _parse_devcontainer_up_output, activate_container, clear_lifecycle_registry, get_lifecycle_tracker, list_active_containers, set_lifecycle_tracker, ) except ModuleNotFoundError: sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) from cleveragents.domain.models.core.container_lifecycle import ( ContainerLifecycleState, ContainerLifecycleTracker, transition_state, validate_transition, ) from cleveragents.resource.handlers.devcontainer import ( _parse_devcontainer_up_output, activate_container, clear_lifecycle_registry, get_lifecycle_tracker, list_active_containers, set_lifecycle_tracker, ) class _MockResult: """Minimal mock result for benchmark subprocess calls.""" def __init__(self) -> None: self.returncode = 0 self.stdout = json.dumps( { "outcome": "success", # F3 fix: use valid 20-char hex ID to pass S1 validation "containerId": "aabbccddee0011223344", "remoteWorkspaceFolder": "/ws", } ) self.stderr = "" def _mock_runner(args: list[str], **kwargs: object) -> _MockResult: """Zero-overhead mock subprocess runner.""" return _MockResult() class TimeTransitionValidation: """Benchmark validate_transition throughput.""" timeout = 10 def time_valid_transition(self) -> None: """Time valid transition check (1000 iterations).""" for _ in range(1000): validate_transition( ContainerLifecycleState.DISCOVERED, ContainerLifecycleState.BUILDING, ) def time_invalid_transition(self) -> None: """Time invalid transition check (1000 iterations).""" for _ in range(1000): validate_transition( ContainerLifecycleState.DISCOVERED, ContainerLifecycleState.RUNNING, ) class TimeTrackerConstruction: """Benchmark lifecycle tracker creation.""" timeout = 10 def time_tracker_construction(self) -> None: """Time ContainerLifecycleTracker instantiation.""" for i in range(1000): ContainerLifecycleTracker( resource_id=f"01BENCH{i:020d}", ) class TimeTransitionState: """Benchmark state transition with history recording.""" timeout = 30 def time_single_transition(self) -> None: """Time a single state transition.""" for _ in range(1000): tracker = ContainerLifecycleTracker( resource_id="01BENCHTRACKER0000000001", ) transition_state( tracker, ContainerLifecycleState.BUILDING, reason="benchmark", ) class TimeActivationLatency: """Benchmark lazy activation with mock runner.""" timeout = 30 params: ClassVar[list[int]] = [1, 10, 50] param_names: ClassVar[list[str]] = ["num_activations"] def setup(self, num_activations: int) -> None: """Clear registry before each timing iteration (R17 fix). Without this, trackers from prior iterations accumulate in ``_lifecycle_registry`` and subsequent activate calls fail with invalid-transition errors (``running`` → ``building``). """ clear_lifecycle_registry() def time_activation(self, num_activations: int) -> None: """Time container activation with mock runner. R8-F6 fix: clear the registry after the loop to stop health check threads spawned by each activation (F3 auto-start). Without this, ``num_activations`` daemon threads accumulate during the timing call. """ for i in range(num_activations): activate_container( f"01BENCHACTIVATE{i:011d}", "/workspace", run_command=_mock_runner, ) clear_lifecycle_registry() def teardown(self, num_activations: int) -> None: """Clear registry after each timing iteration (safety net).""" clear_lifecycle_registry() class TimeJsonParsing: """Benchmark devcontainer up JSON output parsing.""" timeout = 10 def setup(self) -> None: """Create sample JSON output.""" self.valid_json = json.dumps( { "outcome": "success", "containerId": "aabbccddee0011223344", "remoteWorkspaceFolder": "/workspaces/project", } ) self.invalid_json = "not valid json {{" def time_parse_valid_json(self) -> None: """Time parsing valid JSON output.""" for _ in range(1000): _parse_devcontainer_up_output(self.valid_json) def time_parse_invalid_json(self) -> None: """Time parsing invalid JSON output.""" for _ in range(1000): _parse_devcontainer_up_output(self.invalid_json) class TimeRegistryOperations: """Benchmark lifecycle registry get/set/list operations.""" timeout = 30 def setup(self) -> None: """Pre-populate registry with trackers.""" clear_lifecycle_registry() for i in range(100): tracker = ContainerLifecycleTracker( resource_id=f"01BENCHREG{i:016d}", current_state=ContainerLifecycleState.RUNNING if i % 2 == 0 else ContainerLifecycleState.DISCOVERED, ) set_lifecycle_tracker(tracker) def teardown(self) -> None: """Clear registry.""" clear_lifecycle_registry() def time_get_tracker(self) -> None: """Time registry lookup.""" for i in range(1000): get_lifecycle_tracker(f"01BENCHREG{(i % 100):016d}") def time_list_active(self) -> None: """Time listing active containers.""" for _ in range(100): list_active_containers()