619e8eff36
- Adds a --clone-into option to the container-instance command to clone repository contents into a specified path during container setup. - Fixes the devcontainer-instance sandbox strategy to ensure proper isolation, correct mount permissions, and deterministic behavior across environments. - Updates related validation and error handling to reflect the new option and sandbox changes. ISSUES CLOSED: #7555
221 lines
6.7 KiB
Python
221 lines
6.7 KiB
Python
"""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()
|