diff --git a/metrics/__init__.py b/metrics/__init__.py index 63f99dd..e69de29 100644 --- a/metrics/__init__.py +++ b/metrics/__init__.py @@ -1 +0,0 @@ -from .benchmarks import * \ No newline at end of file diff --git a/metrics/__main__.py b/metrics/__main__.py index 6e7f0c4..dcaa5d6 100644 --- a/metrics/__main__.py +++ b/metrics/__main__.py @@ -1,48 +1,15 @@ -from concurrent.futures import ProcessPoolExecutor -import os +from argenta import App, Orchestrator +from argenta.app import DynamicDividingLine -from rich.console import Console -from rich.table import Table -from rich.panel import Panel -from rich.text import Text +from .handlers import router -from metrics.utils import run_benchmark, BenchmarkResult -from .registry import Benchmarks, Benchmark +app = App(initial_message="metrics", prompt=">>> ", dividing_line=DynamicDividingLine('~')) +orchestrator = Orchestrator() -def main(): - console = Console() - all_benchmarks: list[Benchmark] = Benchmarks.get_benchmarks() - - workers = os.cpu_count() or 1 - with ProcessPoolExecutor(max_workers=workers) as executor: - results = executor.map(run_benchmark, all_benchmarks) - - type_paired_benchmarks: dict[str, list[BenchmarkResult]] = {} - - for result in results: - type_paired_benchmarks.setdefault(result.type_, []).append(result) - - for type_, benchmarks in type_paired_benchmarks.items(): - header_text = Text(f"TYPE: {type_.upper()}", style="bold magenta") - console.print(Panel(header_text, expand=False, border_style="magenta")) - - table = Table(show_header=True, header_style="bold cyan", border_style="blue", show_lines=True) - table.add_column("Name", style="green") - table.add_column("Description", style="dim") - table.add_column("Iterations", justify="right") - table.add_column("Avg Time (ms)", justify="right", style="bold yellow") - - for benchmark in benchmarks: - table.add_row( - benchmark.name, - benchmark.description, - str(benchmark.iterations), - str(benchmark.avg_time) - ) - - console.print(table) - console.print() +def main() -> None: + app.include_router(router) + orchestrator.start_polling(app) if __name__ == "__main__": diff --git a/metrics/benchmarks/__init__.py b/metrics/benchmarks/__init__.py index 64424de..a8ce586 100644 --- a/metrics/benchmarks/__init__.py +++ b/metrics/benchmarks/__init__.py @@ -1 +1 @@ -from .pre_cycle_setup import * \ No newline at end of file +from .core import * \ No newline at end of file diff --git a/metrics/benchmarks/core/__init__.py b/metrics/benchmarks/core/__init__.py new file mode 100644 index 0000000..64424de --- /dev/null +++ b/metrics/benchmarks/core/__init__.py @@ -0,0 +1 @@ +from .pre_cycle_setup import * \ No newline at end of file diff --git a/metrics/benchmarks/pre_cycle_setup.py b/metrics/benchmarks/core/pre_cycle_setup.py similarity index 99% rename from metrics/benchmarks/pre_cycle_setup.py rename to metrics/benchmarks/core/pre_cycle_setup.py index 4d0e386..d56cb95 100644 --- a/metrics/benchmarks/pre_cycle_setup.py +++ b/metrics/benchmarks/core/pre_cycle_setup.py @@ -7,12 +7,12 @@ __all__ = [ ] from argenta import App -from argenta.router import Router from argenta.command.models import Command from argenta.response import Response +from argenta.router import Router +from ..models import benchmark from ..utils import get_time_of_pre_cycle_setup -from ..registry import benchmark @benchmark(type_="pre_cycle_setup", description="With no aliases") diff --git a/metrics/registry.py b/metrics/benchmarks/models.py similarity index 91% rename from metrics/registry.py rename to metrics/benchmarks/models.py index 40c9537..efe7ead 100644 --- a/metrics/registry.py +++ b/metrics/benchmarks/models.py @@ -1,14 +1,26 @@ __all__ = [ "Benchmark", "Benchmarks", + "BenchmarkResult", "benchmark" ] +from dataclasses import dataclass +from decimal import Decimal from typing import Callable, ClassVar, overload, override BenchmarkAsFunc = Callable[[], float] +@dataclass(frozen=True) +class BenchmarkResult: + type_: str + name: str + description: str + iterations: int + avg_time: Decimal + + class Benchmark: def __init__( self, diff --git a/metrics/benchmarks/utils.py b/metrics/benchmarks/utils.py new file mode 100644 index 0000000..5ad0f78 --- /dev/null +++ b/metrics/benchmarks/utils.py @@ -0,0 +1,89 @@ +__all__ = [ + "get_time_of_pre_cycle_setup", + "attempts_to_average", + "run_benchmark", + "run_all_benchmarks", + "get_kernel_version" +] + +import io +import os +import platform +import sys +import time +from concurrent.futures import ProcessPoolExecutor +from contextlib import redirect_stdout +from decimal import ROUND_HALF_UP, Decimal + +from argenta import App +from .models import Benchmark, BenchmarkResult, Benchmarks + + +def get_time_of_pre_cycle_setup(app: App) -> float: + start = time.perf_counter() + with redirect_stdout(io.StringIO()): + app._pre_cycle_setup() # pyright: ignore[reportPrivateUsage] + end = time.perf_counter() + return (end - start) * 1000 # as milliseconds + + +def get_kernel_version() -> dict[str, str]: + system = platform.system() + + if system == "Windows": + ver = sys.getwindowsversion() + kernel_version = f"{ver.major}.{ver.minor}.{ver.build}" + + if ver.build >= 22000: + product_name = "Windows 11" + else: + product_name = "Windows 10" + + return { + 'kernel_version': kernel_version, + 'product_name': product_name + } + + elif system == "Linux": + return { + 'kernel_version': platform.release(), + 'product_name': platform.system() + } + + elif system == "Darwin": + return { + 'kernel_version': platform.release(), + 'product_name': f"macOS {platform.mac_ver()[0]}" + } + else: + return { + 'kernel_version': platform.release(), + 'product_name': platform.system(), + } + + +def attempts_to_average(bench_attempts: list[float], iterations: int) -> Decimal: + return Decimal(sum(bench_attempts) / iterations).quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP) + + +def run_all_benchmarks() -> dict[str, list[BenchmarkResult]]: + all_benchmarks: list[Benchmark] = Benchmarks.get_benchmarks() + + workers = os.cpu_count() or 1 + with ProcessPoolExecutor(max_workers=workers) as executor: + results = executor.map(run_benchmark, all_benchmarks) + + type_paired_benchmarks: dict[str, list[BenchmarkResult]] = {} + + for result in results: + type_paired_benchmarks.setdefault(result.type_, []).append(result) + + return type_paired_benchmarks + + +def run_benchmark(benchmark: Benchmark) -> BenchmarkResult: + bench_attempts: list[float] = [] + for _ in range(benchmark.iterations): + bench_attempts.append(benchmark.run()) + avg = attempts_to_average(bench_attempts, benchmark.iterations) + return BenchmarkResult(benchmark.type_, benchmark.name, benchmark.description, benchmark.iterations, avg) diff --git a/metrics/handlers.py b/metrics/handlers.py new file mode 100644 index 0000000..aed1984 --- /dev/null +++ b/metrics/handlers.py @@ -0,0 +1,70 @@ +import platform +import cpuinfo + +from rich.console import Console +from rich.panel import Panel +from rich.table import Table +from rich.text import Text + +from argenta.command.models import Command +from argenta.response import Response +from argenta.router import Router +from .benchmarks.models import BenchmarkResult +from .benchmarks.utils import run_all_benchmarks, get_kernel_version + +console = Console() +router = Router(title="Metrics commands:") + + +@router.command(Command("all-print", description="Print all benchmarks results")) +def all_print_handler(_: Response) -> None: + cpu_info = cpuinfo.get_cpu_info() + os_info = get_kernel_version() + + table = Table(show_header=True, header_style="bold cyan", border_style="blue", show_lines=True) + table.add_column("Parameter", style="green") + table.add_column("Value", style="yellow") + + table.add_row("OS", platform.system()) + table.add_row("OS Name", os_info['product_name']) + table.add_row("OS Kernel Version", os_info['kernel_version']) + table.add_row("Architecture", cpu_info['arch']) + table.add_row("Processor", cpu_info['brand_raw']) + table.add_row("Python Version", cpu_info['python_version']) + table.add_row("Python Implementation", platform.python_implementation()) + + header_text = Text("SYSTEM INFO", style="bold magenta") + console.print(Panel(header_text, expand=False, border_style="magenta")) + console.print(table, end="\n\n") + + type_paired_benchmarks: dict[str, list[BenchmarkResult]] = run_all_benchmarks() + + for type_, benchmarks in type_paired_benchmarks.items(): + header_text = Text(f"TYPE: {type_.upper()}", style="bold magenta") + console.print(Panel(header_text, expand=False, border_style="magenta")) + + table = Table(show_header=True, header_style="bold cyan", border_style="blue", show_lines=True) + table.add_column("Name", style="green") + table.add_column("Description", style="dim") + table.add_column("Iterations", justify="right") + table.add_column("Avg Time (ms)", justify="right", style="bold yellow") + + for benchmark in benchmarks: + table.add_row( + benchmark.name, + benchmark.description, + str(benchmark.iterations), + str(benchmark.avg_time) + ) + + console.print(table) + + +@router.command(Command("release-generate", description="Generate release report")) +def release_generate_handler(_: Response) -> None: + console.print("[yellow]Release report generation not implemented yet[/yellow]") + + +@router.command(Command("diagrams-generate", description="Generate diagrams")) +def diagrams_generate_handler(_: Response) -> None: + console.print("[yellow]Diagrams generation not implemented yet[/yellow]") diff --git a/metrics/utils.py b/metrics/utils.py deleted file mode 100644 index 2153323..0000000 --- a/metrics/utils.py +++ /dev/null @@ -1,44 +0,0 @@ -__all__ = [ - "get_time_of_pre_cycle_setup", - "attempts_to_average", - "run_benchmark", - "BenchmarkResult" -] - -import io -from contextlib import redirect_stdout -import time -from dataclasses import dataclass -from decimal import Decimal, ROUND_HALF_UP - -from argenta import App -from metrics.registry import Benchmark - - -def get_time_of_pre_cycle_setup(app: App) -> float: - start = time.perf_counter() - with redirect_stdout(io.StringIO()): - app._pre_cycle_setup() # pyright: ignore[reportPrivateUsage] - end = time.perf_counter() - return (end - start) * 1000 # as milliseconds - - -def attempts_to_average(bench_attempts: list[float], iterations: int) -> Decimal: - return Decimal(sum(bench_attempts) / iterations).quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP) - - -@dataclass(frozen=True) -class BenchmarkResult: - type_: str - name: str - description: str - iterations: int - avg_time: Decimal - - -def run_benchmark(benchmark: Benchmark) -> BenchmarkResult: - bench_attempts: list[float] = [] - for _ in range(benchmark.iterations): - bench_attempts.append(benchmark.run()) - avg = attempts_to_average(bench_attempts, benchmark.iterations) - return BenchmarkResult(benchmark.type_, benchmark.name, benchmark.description, benchmark.iterations, avg) diff --git a/pyproject.toml b/pyproject.toml index eeabf62..560c6d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,9 @@ tests = [ "pytest-cov>=7.0.0", "pytest-mock>=3.15.1", ] +metrics = [ + "py-cpuinfo>=9.0.0", +] [tool.ruff] exclude = [ diff --git a/src/argenta/app/protocols.py b/src/argenta/app/protocols.py index c5232f6..d68614d 100644 --- a/src/argenta/app/protocols.py +++ b/src/argenta/app/protocols.py @@ -1,6 +1,6 @@ __all__ = ["NonStandardBehaviorHandler", "EmptyCommandHandler", "Printer", "DescriptionMessageGenerator", "HandlerFunc"] -from typing import ParamSpec, Protocol, TypeVar +from typing import Any, ParamSpec, Protocol, TypeVar from argenta.response import Response T = TypeVar("T", contravariant=True) @@ -28,5 +28,5 @@ class DescriptionMessageGenerator(Protocol): class HandlerFunc(Protocol): - def __call__(self, response: Response) -> None: + def __call__(self, response: Response, /, *args: Any, **kwargs: Any) -> None: raise NotImplementedError diff --git a/uv.lock b/uv.lock index d5fb31f..f3ad6ca 100644 --- a/uv.lock +++ b/uv.lock @@ -61,6 +61,9 @@ linters = [ { name = "ruff" }, { name = "wemake-python-styleguide" }, ] +metrics = [ + { name = "py-cpuinfo" }, +] tests = [ { name = "pyfakefs" }, { name = "pytest" }, @@ -92,6 +95,7 @@ linters = [ { name = "ruff", specifier = ">=0.12.12" }, { name = "wemake-python-styleguide", specifier = ">=0.17.0" }, ] +metrics = [{ name = "py-cpuinfo", specifier = ">=9.0.0" }] tests = [ { name = "pyfakefs", specifier = ">=5.5.0" }, { name = "pytest", specifier = ">=8.3.2" }, @@ -589,6 +593,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "py-cpuinfo" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/a8/d832f7293ebb21690860d2e01d8115e5ff6f2ae8bbdc953f0eb0fa4bd2c7/py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690", size = 104716, upload-time = "2022-10-25T20:38:06.303Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/a9/023730ba63db1e494a271cb018dcd361bd2c917ba7004c3e49d5daf795a2/py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5", size = 22335, upload-time = "2022-10-25T20:38:27.636Z" }, +] + [[package]] name = "pycodestyle" version = "2.14.0"