diff --git a/metrics/__init__.py b/metrics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/metrics/__main__.py b/metrics/__main__.py new file mode 100644 index 0000000..e69de29 diff --git a/metrics/benchmarks/pre_cycle_setup.py b/metrics/benchmarks/pre_cycle_setup.py new file mode 100644 index 0000000..419a34f --- /dev/null +++ b/metrics/benchmarks/pre_cycle_setup.py @@ -0,0 +1,117 @@ +from argenta import App +from argenta.router import Router +from argenta.command.models import Command +from argenta.response import Response + +from ..utils import get_time_of_pre_cycle_setup +from ..registry import benchmark + + +@benchmark(name="Time of pre_cycle_setup", description="With no aliases") +def benchmark_no_aliases() -> float: + app = App(override_system_messages=True) + router = Router() + + @router.command(Command('command1')) + def handler1(_res: Response) -> None: + pass + + @router.command(Command('command2')) + def handler2(_res: Response) -> None: + pass + + @router.command(Command('command3')) + def handler3(_res: Response) -> None: + pass + + app.include_router(router) + execution_time = get_time_of_pre_cycle_setup(app) + return execution_time + + +@benchmark(name="Time of pre_cycle_setup", description="With few aliases (6 total)") +def benchmark_few_aliases() -> float: + app = App(override_system_messages=True) + router = Router() + + @router.command(Command('command1', aliases={'c1', 'cmd1'})) + def handler1(_res: Response) -> None: + pass + + @router.command(Command('command2', aliases={'c2', 'cmd2'})) + def handler2(_res: Response) -> None: + pass + + @router.command(Command('command3', aliases={'c3', 'cmd3'})) + def handler3(_res: Response) -> None: + pass + + app.include_router(router) + execution_time = get_time_of_pre_cycle_setup(app) + return execution_time + + +@benchmark(name="Time of pre_cycle_setup", description="With many aliases (15 total)") +def benchmark_many_aliases() -> float: + app = App(override_system_messages=True) + router = Router() + + @router.command(Command('command1', aliases={'c1', 'cmd1', 'com1', 'first', 'one'})) + def handler1(_res: Response) -> None: + pass + + @router.command(Command('command2', aliases={'c2', 'cmd2', 'com2', 'second', 'two'})) + def handler2(_res: Response) -> None: + pass + + @router.command(Command('command3', aliases={'c3', 'cmd3', 'com3', 'third', 'three'})) + def handler3(_res: Response) -> None: + pass + + app.include_router(router) + execution_time = get_time_of_pre_cycle_setup(app) + return execution_time + + +@benchmark(name="Time of pre_cycle_setup", description="With very many aliases (60 total)") +def benchmark_very_many_aliases() -> float: + app = App(override_system_messages=True) + router = Router() + + @router.command(Command('command1', aliases={f'alias1_{i}' for i in range(20)})) + def handler1(_res: Response) -> None: + pass + + @router.command(Command('command2', aliases={f'alias2_{i}' for i in range(20)})) + def handler2(_res: Response) -> None: + pass + + @router.command(Command('command3', aliases={f'alias3_{i}' for i in range(20)})) + def handler3(_res: Response) -> None: + pass + + app.include_router(router) + execution_time = get_time_of_pre_cycle_setup(app) + return execution_time + + +@benchmark(name="Time of pre_cycle_setup", description="With extreme aliases (300 total)") +def benchmark_extreme_aliases() -> float: + app = App(override_system_messages=True) + router = Router() + + @router.command(Command('command1', aliases={f'alias1_{i}' for i in range(100)})) + def handler1(_res: Response) -> None: + pass + + @router.command(Command('command2', aliases={f'alias2_{i}' for i in range(100)})) + def handler2(_res: Response) -> None: + pass + + @router.command(Command('command3', aliases={f'alias3_{i}' for i in range(100)})) + def handler3(_res: Response) -> None: + pass + + app.include_router(router) + execution_time = get_time_of_pre_cycle_setup(app) + return execution_time diff --git a/metrics/registry.py b/metrics/registry.py new file mode 100644 index 0000000..02d5cb1 --- /dev/null +++ b/metrics/registry.py @@ -0,0 +1,88 @@ +from typing import Any, Callable, ClassVar, ParamSpec, TypeVar, overload, override, Generic + + +P = ParamSpec("P") +R = TypeVar("R", default=float) + + +class Benchmark(Generic[P, R]): + def __init__( + self, + func: Callable[P, R], + *, + name: str, + description: str, + iterations: int + ) -> None: + self.func = func + self.name = name + self.description = description + self.iterations = iterations + + def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: + return self.func(*args, **kwargs) + + @override + def __repr__(self) -> str: + return f'Benchmark<{self.name=}, {self.description=}, {self.iterations=}>' + + @override + def __str__(self) -> str: + return f'Benchmark({self.name=}, {self.description=}, {self.iterations=})' + + +class Benchmarks: + _benchmarks: ClassVar[list[Benchmark[Any, Any]]] = [] + + @overload + @classmethod + def register( + cls, + call: Callable[P, R], + *, + name: str = "", + description: str = "", + iterations: int = 100, + ) -> Callable[P, R]: ... + + @overload + @classmethod + def register( + cls, + call: None = None, + *, + name: str = "", + description: str = "", + iterations: int = 100, + ) -> Callable[[Callable[P, R]], Callable[P, R]]: ... + + @classmethod + def register( + cls, + call: Callable[P, R] | None = None, + *, + name: str = "", + description: str = "", + iterations: int = 100, + ) -> Callable[[Callable[P, R]], Callable[P, R]] | Callable[P, R]: + def decorator(func: Callable[P, R]) -> Callable[P, R]: + cls._benchmarks.append( + Benchmark( + func, + name = name or func.__name__, + description = description or f'description for {name or func.__name__} with {iterations} iterations', + iterations = iterations + ) + ) + return func + + if call is None: + return decorator + else: + return decorator(call) + + @classmethod + def get_benchmarks(cls) -> list[Benchmark[Any, Any]]: + return cls._benchmarks + +benchmark = Benchmarks.register diff --git a/src/argenta/metrics/main.py b/metrics/utils.py similarity index 87% rename from src/argenta/metrics/main.py rename to metrics/utils.py index 12861f7..ac6adf9 100644 --- a/src/argenta/metrics/main.py +++ b/metrics/utils.py @@ -4,7 +4,7 @@ __all__ = [ import io from contextlib import redirect_stdout -from time import time +import time from argenta import App @@ -15,8 +15,8 @@ def get_time_of_pre_cycle_setup(app: App) -> float: :param app: app instance for testing time of pre cycle setup :return: time of pre cycle setup as float """ - start = time() + start = time.monotonic() with redirect_stdout(io.StringIO()): app._pre_cycle_setup() # pyright: ignore[reportPrivateUsage] - end = time() + end = time.monotonic() return end - start diff --git a/pyproject.toml b/pyproject.toml index eb066f7..eeabf62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,11 @@ root = "tests/" reportPrivateUsage = false reportUnusedFunction = false +[[tool.pyright.executionEnvironments]] +root = "metrics/" +reportPrivateUsage = false +reportUnusedFunction = false + [tool.coverage.run] branch = true omit = [ diff --git a/src/argenta/metrics/__init__.py b/src/argenta/metrics/__init__.py deleted file mode 100644 index e97a8ca..0000000 --- a/src/argenta/metrics/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from argenta.metrics.main import \ - get_time_of_pre_cycle_setup as get_time_of_pre_cycle_setup