Files
cleveragents-core/benchmarks/devcontainer_handler_bench.py
T
freemo 279c6112ae feat(resource): add devcontainer resource type and auto-discovery handler
Added three new built-in resource types: container-instance,
devcontainer-instance, and devcontainer-file. The devcontainer-instance
type inherits from container-instance per ADR-042 and is auto-discovered
when git-checkout or fs-directory resources contain .devcontainer/
directories per ADR-043.

Implementation includes:
- DevcontainerHandler extending BaseResourceHandler with snapshot strategy
- Auto-discovery module scanning .devcontainer/devcontainer.json and
  root .devcontainer.json with JSON validation
- CLI support for devcontainer-instance and container-instance via
  agents resource add with --path and --image flags
- Behave feature with 22 scenarios covering manual registration,
  auto-discovery, invalid JSON handling, and protocol conformance
- Robot integration tests with 10 test cases for CLI round-trip and
  DAG hierarchy validation
- ASV benchmarks measuring discovery throughput with varying subdirectory
  counts, handler resolver cache performance, and result construction
- Reference documentation at docs/reference/devcontainer_resources.md

ISSUES CLOSED: #511
2026-03-02 22:09:37 -05:00

166 lines
5.0 KiB
Python

"""ASV benchmarks for devcontainer handler auto-discovery throughput.
Measures the performance of:
- Devcontainer discovery on projects with many subdirectories
- Handler resolver cache performance for DevcontainerHandler
- Discovery result construction overhead
- is_trigger_type check throughput
"""
from __future__ import annotations
import json
import sys
import tempfile
from pathlib import Path
try:
from cleveragents.resource.handlers.devcontainer import (
DevcontainerHandler,
)
from cleveragents.resource.handlers.discovery import (
DevcontainerDiscoveryResult,
discover_devcontainers,
is_trigger_type,
)
from cleveragents.resource.handlers.resolver import (
clear_handler_cache,
resolve_handler,
)
except ModuleNotFoundError:
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from cleveragents.resource.handlers.devcontainer import (
DevcontainerHandler,
)
from cleveragents.resource.handlers.discovery import (
DevcontainerDiscoveryResult,
discover_devcontainers,
is_trigger_type,
)
from cleveragents.resource.handlers.resolver import (
clear_handler_cache,
resolve_handler,
)
_VALID_DC_JSON: str = json.dumps(
{"name": "bench-devcontainer", "image": "ubuntu:latest"}
)
class TimeDiscoveryThroughput:
"""Benchmark auto-discovery throughput on projects with many subdirs."""
timeout = 60
params: list[int] = [10, 50, 100]
param_names: list[str] = ["num_subdirs"]
def setup(self, num_subdirs: int) -> None:
"""Create a temp directory with many subdirectories."""
self.tmp_dir = tempfile.mkdtemp()
base = Path(self.tmp_dir)
# Create the devcontainer config
dc_dir = base / ".devcontainer"
dc_dir.mkdir()
(dc_dir / "devcontainer.json").write_text(_VALID_DC_JSON, encoding="utf-8")
# Create many sibling directories (noise)
for i in range(num_subdirs):
(base / f"subdir_{i:04d}").mkdir()
def teardown(self, num_subdirs: int) -> None:
"""Clean up temp directory."""
import shutil
shutil.rmtree(self.tmp_dir, ignore_errors=True)
def time_discovery_with_subdirs(self, num_subdirs: int) -> None:
"""Time discovery scanning with many peer directories."""
discover_devcontainers(self.tmp_dir, "git-checkout")
class TimeDiscoveryEmpty:
"""Benchmark discovery on directory with no devcontainer."""
timeout = 30
def setup(self) -> None:
"""Create empty temp directory."""
self.tmp_dir = tempfile.mkdtemp()
base = Path(self.tmp_dir)
for i in range(50):
(base / f"subdir_{i:04d}").mkdir()
def teardown(self) -> None:
"""Clean up."""
import shutil
shutil.rmtree(self.tmp_dir, ignore_errors=True)
def time_discovery_no_match(self) -> None:
"""Time discovery when no devcontainer exists."""
discover_devcontainers(self.tmp_dir, "git-checkout")
class TimeHandlerResolver:
"""Benchmark handler resolver cache for DevcontainerHandler."""
timeout = 30
_ref = "cleveragents.resource.handlers.devcontainer:DevcontainerHandler"
def setup(self) -> None:
"""Pre-warm resolver cache."""
clear_handler_cache()
resolve_handler(self._ref)
def time_cached_resolution(self) -> None:
"""Time cached handler resolution (should be near-zero)."""
resolve_handler(self._ref)
def time_cold_resolution(self) -> None:
"""Time cold handler resolution (import + instantiate)."""
clear_handler_cache()
resolve_handler(self._ref)
class TimeIsTriggerType:
"""Benchmark is_trigger_type check throughput."""
timeout = 10
def time_trigger_type_positive(self) -> None:
"""Time is_trigger_type for a trigger type."""
for _ in range(1000):
is_trigger_type("git-checkout")
def time_trigger_type_negative(self) -> None:
"""Time is_trigger_type for a non-trigger type."""
for _ in range(1000):
is_trigger_type("devcontainer-instance")
class TimeDiscoveryResult:
"""Benchmark DevcontainerDiscoveryResult construction."""
timeout = 30
def setup(self) -> None:
"""Create temp file for result construction."""
self.tmp_dir = tempfile.mkdtemp()
self.dc_file = Path(self.tmp_dir) / "devcontainer.json"
self.dc_file.write_text(_VALID_DC_JSON, encoding="utf-8")
self.config_data: dict[str, object] = json.loads(_VALID_DC_JSON)
def teardown(self) -> None:
"""Clean up."""
import shutil
shutil.rmtree(self.tmp_dir, ignore_errors=True)
def time_result_construction(self) -> None:
"""Time DevcontainerDiscoveryResult instantiation."""
for _ in range(1000):
DevcontainerDiscoveryResult(
config_path=self.dc_file,
config_data=self.config_data,
parent_location=self.tmp_dir,
)