Files
cleveragents-core/benchmarks/changeset_persistence_bench.py
freemo e3fcce413b
CI / lint (pull_request) Successful in 24s
CI / typecheck (pull_request) Successful in 59s
CI / security (pull_request) Successful in 53s
CI / quality (pull_request) Successful in 36s
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 27s
CI / integration_tests (pull_request) Successful in 5m30s
CI / benchmark-regression (pull_request) Successful in 25m38s
CI / unit_tests (pull_request) Successful in 36m51s
CI / docker (pull_request) Successful in 1m3s
CI / coverage (pull_request) Successful in 1h48m49s
CI / lint (push) Successful in 22s
CI / security (push) Successful in 58s
CI / typecheck (push) Successful in 1m3s
CI / quality (push) Successful in 46s
CI / build (push) Successful in 23s
CI / integration_tests (push) Successful in 5m23s
CI / benchmark-regression (push) Has been skipped
CI / benchmark-publish (push) Successful in 15m5s
CI / unit_tests (push) Successful in 20m1s
CI / docker (push) Successful in 1m0s
CI / coverage (push) Successful in 1h45m36s
feat(changeset): persist changesets and diff artifacts
Add SQLite persistence for ChangeSet entries and ToolInvocation records
via new changeset_repository module implementing the ChangeSetStore protocol.

- Alembic migration d0_001 creates changeset_entries and tool_invocations tables
- SqliteChangeSetStore, ChangeSetEntryRepository, ToolInvocationRepository
- PlanApplyService.cleanup_changeset() for cancel/failure cleanup
- Behave tests (17 scenarios), Robot tests (5 cases), ASV benchmarks
- Reference documentation in docs/reference/changeset.md

Closes #163
2026-02-26 02:47:14 +00:00

99 lines
3.1 KiB
Python

"""ASV benchmarks for ChangeSet persistence overhead.
Measures the cost of persisting ChangeEntry records and retrieving
them via the SqliteChangeSetStore, simulating typical plan execution
workloads.
"""
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
from cleveragents.domain.models.core.change import (
ChangeEntry,
ChangeOperation,
)
from cleveragents.infrastructure.database.changeset_repository import (
ChangeSetEntryRepository,
SqliteChangeSetStore,
)
from cleveragents.infrastructure.database.models import Base
def _make_factory() -> tuple[sessionmaker[Session], object]:
"""Create an in-memory SQLite engine + session factory."""
engine = create_engine(
"sqlite:///:memory:",
echo=False,
connect_args={"check_same_thread": False},
)
Base.metadata.create_all(engine)
factory: sessionmaker[Session] = sessionmaker(bind=engine)
return factory, engine
def _make_entry(plan_id: str, idx: int) -> ChangeEntry:
return ChangeEntry(
plan_id=plan_id,
resource_id="res-001",
tool_name="file-write",
operation=ChangeOperation.CREATE,
path=f"src/file_{idx}.py",
after_hash=f"hash_{idx:06d}",
)
class TimeChangeSetPersistence:
"""Benchmark suite for ChangeSet persistence operations."""
timeout = 60.0
def setup(self) -> None:
self.factory, self.engine = _make_factory()
self.store = SqliteChangeSetStore(self.factory)
self.plan_id = "bench-plan-001"
self.changeset_id = self.store.start(self.plan_id)
# Pre-populate for retrieval benchmarks
for i in range(50):
entry = _make_entry(self.plan_id, i)
self.store.record(self.changeset_id, entry)
session = self.factory()
session.commit()
session.close()
def time_record_single_entry(self) -> None:
"""Time recording a single ChangeEntry."""
entry = _make_entry(self.plan_id, 999)
self.store.record(self.changeset_id, entry)
def time_get_changeset_50_entries(self) -> None:
"""Time retrieving a changeset with 50 entries."""
self.store.get(self.changeset_id)
def time_summarize_changeset(self) -> None:
"""Time computing summary for a changeset."""
self.store.summarize(self.changeset_id)
def time_start_changeset(self) -> None:
"""Time starting a new empty changeset."""
self.store.start("bench-plan-new")
class TimeEntryRepositoryBulk:
"""Benchmark bulk entry operations."""
timeout = 60.0
def setup(self) -> None:
self.factory, self.engine = _make_factory()
self.repo = ChangeSetEntryRepository(self.factory)
def time_save_100_entries(self) -> None:
"""Time saving 100 entries sequentially."""
for i in range(100):
entry = _make_entry("bench-bulk", i)
self.repo.save_entry(f"cs-bulk-{i % 5}", entry)
def time_retrieve_by_plan(self) -> None:
"""Time retrieving all entries for a plan."""
self.repo.get_entries_for_plan("bench-bulk")