Files

210 lines
6.4 KiB
Python

"""ASV benchmarks for UnitOfWork lifecycle repository wiring.
Measures overhead of creating UnitOfWorkContext and accessing
actions / lifecycle_plans repositories.
"""
from __future__ import annotations
from datetime import datetime
from sqlalchemy import create_engine, event
from sqlalchemy.orm import Session, sessionmaker
from cleveragents.domain.models.core.action import Action, ActionState
from cleveragents.domain.models.core.plan import (
NamespacedName,
PlanIdentity,
PlanPhase,
PlanTimestamps,
ProcessingState,
)
from cleveragents.domain.models.core.plan import Plan as V3Plan
from cleveragents.infrastructure.database.models import Base
from cleveragents.infrastructure.database.unit_of_work import UnitOfWorkContext
_CB32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
_bench_counter = 5000
def _bench_ulid() -> str:
global _bench_counter
_bench_counter += 1
n = _bench_counter
suffix = ""
for _ in range(8):
suffix = _CB32[n % 32] + suffix
n //= 32
return f"01HGZ6FE0AQDYTR4BX{suffix}"
def _make_bench_action(name: str = "local/bench-uow-action") -> Action:
parts = name.split("/", 1)
namespace = parts[0] if len(parts) == 2 else "local"
short_name = parts[1] if len(parts) == 2 else parts[0]
now = datetime.now()
return Action(
namespaced_name=NamespacedName(namespace=namespace, name=short_name),
description=f"Bench action {short_name}",
long_description=None,
definition_of_done="Done",
strategy_actor="local/s",
execution_actor="local/e",
estimation_actor=None,
review_actor=None,
arguments=[],
reusable=True,
read_only=False,
state=ActionState("available"),
created_at=now,
updated_at=now,
created_by=None,
tags=[],
)
def _make_bench_plan(
plan_id: str | None = None,
action_name: str = "local/bench-uow-action",
plan_name: str | None = None,
) -> V3Plan:
now = datetime.now()
name = plan_name or f"bench-uow-plan-{_bench_ulid()}"
return V3Plan(
identity=PlanIdentity(plan_id=plan_id or _bench_ulid(), attempt=1),
namespaced_name=NamespacedName(namespace="local", name=name),
action_name=action_name,
description="Bench plan",
definition_of_done="Done",
strategy_actor="local/s",
execution_actor="local/e",
phase=PlanPhase("action"),
processing_state=ProcessingState("queued"),
timestamps=PlanTimestamps(created_at=now, updated_at=now),
project_links=[],
arguments={},
arguments_order=[],
invariants=[],
reusable=True,
read_only=False,
created_by=None,
tags=[],
)
class TimeUowContextCreation:
"""Benchmark UnitOfWorkContext instantiation."""
timeout = 30.0
def setup(self) -> None:
self.engine = create_engine(
"sqlite:///:memory:",
echo=False,
future=True,
connect_args={"check_same_thread": False},
)
@event.listens_for(self.engine, "connect")
def _fk(dbapi_conn, _rec): # type: ignore[no-untyped-def]
cursor = dbapi_conn.cursor()
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()
Base.metadata.create_all(self.engine)
self.sf: sessionmaker[Session] = sessionmaker(
bind=self.engine,
expire_on_commit=False,
autoflush=False,
autocommit=False,
class_=Session,
)
def time_create_context(self) -> None:
session = self.sf()
_ = UnitOfWorkContext(session)
session.close()
def time_access_actions_repo(self) -> None:
session = self.sf()
ctx = UnitOfWorkContext(session)
_ = ctx.actions
session.close()
def time_access_lifecycle_plans_repo(self) -> None:
session = self.sf()
ctx = UnitOfWorkContext(session)
_ = ctx.lifecycle_plans
session.close()
class TimeUowActionPlanRoundTrip:
"""Benchmark creating action + plan via UoW and retrieving."""
timeout = 30.0
def setup(self) -> None:
self.engine = create_engine(
"sqlite:///:memory:",
echo=False,
future=True,
connect_args={"check_same_thread": False},
)
@event.listens_for(self.engine, "connect")
def _fk(dbapi_conn, _rec): # type: ignore[no-untyped-def]
cursor = dbapi_conn.cursor()
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()
Base.metadata.create_all(self.engine)
self.sf: sessionmaker[Session] = sessionmaker(
bind=self.engine,
expire_on_commit=False,
autoflush=False,
autocommit=False,
class_=Session,
)
# Seed an action for plan creation benchmarks
session = self.sf()
ctx = UnitOfWorkContext(session)
ctx.actions.create(_make_bench_action("local/bench-uow-action"))
session.commit()
session.close()
def time_create_action_via_uow(self) -> None:
session = self.sf()
ctx = UnitOfWorkContext(session)
uid = _bench_ulid()
ctx.actions.create(_make_bench_action(f"local/bench-action-{uid}"))
session.commit()
session.close()
def time_create_plan_via_uow(self) -> None:
session = self.sf()
ctx = UnitOfWorkContext(session)
ctx.lifecycle_plans.create(_make_bench_plan())
session.commit()
session.close()
def time_create_action_and_plan_via_uow(self) -> None:
uid = _bench_ulid()
# NamespacedName.validate_name lowercases the name, so the action
# is stored with a lowercased namespaced_name PK. The plan's
# action_name (a plain-string FK) must match exactly.
action_name = f"local/bench-combo-{uid}".lower()
# Commit the action first so the FK reference is durable before
# the plan repository (which has its own retry/rollback logic)
# attempts the insert.
session = self.sf()
ctx = UnitOfWorkContext(session)
ctx.actions.create(_make_bench_action(action_name))
session.commit()
session.close()
session = self.sf()
ctx = UnitOfWorkContext(session)
ctx.lifecycle_plans.create(_make_bench_plan(action_name=action_name))
session.commit()
session.close()