Files
cleveragents-core/benchmarks/context_indexing_bench.py
aditya 4e64544aae
CI / benchmark-publish (pull_request) Has been skipped
CI / quality (pull_request) Successful in 48s
CI / build (pull_request) Successful in 31s
CI / helm (pull_request) Successful in 21s
CI / lint (pull_request) Successful in 3m15s
CI / typecheck (pull_request) Successful in 4m4s
CI / security (pull_request) Successful in 4m13s
CI / unit_tests (pull_request) Successful in 4m28s
CI / docker (pull_request) Successful in 1m52s
CI / integration_tests (pull_request) Successful in 7m14s
CI / coverage (pull_request) Successful in 12m49s
CI / e2e_tests (pull_request) Successful in 22m8s
CI / status-check (pull_request) Successful in 2s
CI / benchmark-regression (pull_request) Successful in 57m27s
feat(acms): projects with 10,000+ files index without timeout
Add explicit indexing timeout bounds and propagate timeout_seconds through the repo indexing service, utility walker, and CLI entrypoint so large repositories fail predictably instead of hanging.

Add 10K-scale Behave/Robot verification and a dedicated benchmark path to validate that large-project indexing completes, persists status correctly, and remains measurable for regressions.

Address review feedback by splitting oversized Behave and Robot support files into focused modules, trimming repo_indexing_service.py back under the 500-line limit, and rebasing the branch onto current master.

ISSUES CLOSED: #851

# Conflicts:
#	CHANGELOG.md
#	robot/helper_m5_e2e_verification.py
2026-03-30 10:18:19 +00:00

156 lines
5.1 KiB
Python

"""ASV benchmarks for repo indexing service (issue #195).
Measures indexing throughput, incremental refresh overhead, and
language detection performance.
"""
from __future__ import annotations
import shutil
import sys
import tempfile
from pathlib import Path
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
from sqlalchemy import create_engine # noqa: E402
from sqlalchemy.orm import sessionmaker # noqa: E402
import cleveragents # noqa: E402, F401
from cleveragents.application.services.repo_indexing_service import ( # noqa: E402
RepoIndexingService,
detect_language,
estimate_tokens,
)
from cleveragents.infrastructure.database.models import Base # noqa: E402
_RID = "01KK0D8WNATFNEX2JMG5GKF6FP"
def _make_service() -> RepoIndexingService:
engine = create_engine("sqlite:///:memory:", echo=False)
Base.metadata.create_all(engine)
factory = sessionmaker(bind=engine, expire_on_commit=False)
return RepoIndexingService(session_factory=factory)
class TimeRepoIndexing:
"""Benchmark repo indexing throughput."""
timeout = 120
def setup(self) -> None:
"""Create a temporary project with 100 files."""
self.svc = _make_service()
self.tmpdir = tempfile.mkdtemp()
for i in range(50):
(Path(self.tmpdir) / f"module_{i:03d}.py").write_text(
f"# Module {i}\ndef func_{i}():\n return {i}\n" * 10
)
for i in range(30):
(Path(self.tmpdir) / f"component_{i:03d}.ts").write_text(
f"export const comp{i} = () => {{}};\n" * 10
)
for i in range(20):
(Path(self.tmpdir) / f"doc_{i:03d}.md").write_text(
f"# Doc {i}\n\nContent for document {i}.\n" * 5
)
def teardown(self) -> None:
shutil.rmtree(self.tmpdir, ignore_errors=True)
def time_full_index_100_files(self) -> None:
"""Full index of 100 files."""
self.svc.index_resource(_RID, self.tmpdir)
def time_incremental_refresh_no_changes(self) -> None:
"""Incremental refresh when no files changed (100 files)."""
# Pre-index to ensure an index exists, then measure refresh only.
self.svc.index_resource(_RID, self.tmpdir)
self.svc.refresh_index(_RID, self.tmpdir)
def time_refresh_only_no_changes(self) -> None:
"""Pure refresh measurement (index already exists).
Note: ASV times the entire method body including the pre-index
call. For a purer measurement, use ``setup`` to pre-index and
only call ``refresh_index`` in the timed body.
"""
self.svc.index_resource(_RID, self.tmpdir)
self.svc.refresh_index(_RID, self.tmpdir)
def time_language_detection_1000(self) -> None:
"""Language detection for 1000 file paths."""
paths = (
[f"module_{i}.py" for i in range(250)]
+ [f"component_{i}.ts" for i in range(250)]
+ [f"doc_{i}.md" for i in range(250)]
+ [f"data_{i}.json" for i in range(250)]
)
for p in paths:
detect_language(p)
def time_token_estimation_1000(self) -> None:
"""Token estimation for 1000 sizes."""
for size in range(1000):
estimate_tokens(size * 100)
class TrackRepoIndexing:
"""Track metrics for repo indexing."""
timeout = 120
def setup(self) -> None:
self.svc = _make_service()
self.tmpdir = tempfile.mkdtemp()
for i in range(50):
(Path(self.tmpdir) / f"module_{i:03d}.py").write_text(
f"def func_{i}():\n return {i}\n" * 10
)
def teardown(self) -> None:
shutil.rmtree(self.tmpdir, ignore_errors=True)
def track_file_count(self) -> int:
"""Track number of files indexed."""
idx = self.svc.index_resource(_RID, self.tmpdir)
return idx.metadata.file_count
track_file_count.unit = "files" # type: ignore[attr-defined]
def track_token_estimate(self) -> int:
"""Track total token estimate."""
idx = self.svc.index_resource(_RID, self.tmpdir)
return idx.metadata.token_estimate
track_token_estimate.unit = "tokens" # type: ignore[attr-defined]
class TimeRepoIndexingLarge:
"""Benchmark indexing throughput for 10,000-file repositories."""
timeout = 1200
def setup(self) -> None:
"""Create a temporary project with exactly 10,000 Python files."""
self.svc = _make_service()
self.tmpdir = tempfile.mkdtemp()
root = Path(self.tmpdir)
for i in range(100):
module_dir = root / f"module_{i:03d}"
module_dir.mkdir(parents=True, exist_ok=True)
for j in range(100):
(module_dir / f"file_{j:03d}.py").write_text(
f"def f_{i}_{j}():\n return {i + j}\n"
)
def teardown(self) -> None:
shutil.rmtree(self.tmpdir, ignore_errors=True)
def time_full_index_10000_files(self) -> None:
"""Full index for a 10,000-file repository."""
self.svc.index_resource(_RID, self.tmpdir, timeout_seconds=120.0)