Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ classifiers = [
"Typing :: Typed",
]
dependencies = [
"cffi >= 1.17.1",
"pytest>=3.8",
"rich>=13.8.1",
"importlib-metadata>=8.5.0; python_version < '3.10'",
Expand Down Expand Up @@ -64,12 +63,17 @@ dev = [
pytest11 = { codspeed = "pytest_codspeed.plugin" }

[build-system]
requires = ["setuptools >= 61", "cffi >= 1.17.1"]
requires = ["setuptools >= 61"]
build-backend = "setuptools.build_meta"

[tool.setuptools]
license-files = [] # Workaround of https://github.com/astral-sh/uv/issues/9513

[tool.setuptools.package-data]
pytest_codspeed = [
"instruments/hooks/instrument-hooks/includes/*.h",
]

[tool.setuptools.dynamic]
version = { attr = "pytest_codspeed.__version__" }

Expand Down
34 changes: 14 additions & 20 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
import importlib.util
import os
import platform
from pathlib import Path

from setuptools import setup

build_path = Path(__file__).parent / "src/pytest_codspeed/instruments/hooks/build.py"

spec = importlib.util.spec_from_file_location("build", build_path)
assert spec is not None, "The spec should be initialized"
build = importlib.util.module_from_spec(spec)
assert spec.loader is not None, "The loader should be initialized"
spec.loader.exec_module(build)
from setuptools import Extension, setup

system = platform.system()
current_arch = platform.machine()
Expand Down Expand Up @@ -39,22 +29,26 @@
"The extension is required but the current platform is not supported"
)

ffi_extension = build.ffibuilder.distutils_extension()
ffi_extension.optional = not IS_EXTENSION_REQUIRED
# Build native C extension
native_extension = Extension(
"pytest_codspeed.instruments.hooks.dist_instrument_hooks",
sources=[
"src/pytest_codspeed/instruments/hooks/instrument_hooks_module.c",
"src/pytest_codspeed/instruments/hooks/instrument-hooks/dist/core.c",
],
include_dirs=["src/pytest_codspeed/instruments/hooks/instrument-hooks/includes"],
optional=not IS_EXTENSION_REQUIRED,
)

print(
"CodSpeed native extension is "
+ ("required" if IS_EXTENSION_REQUIRED else "optional")
)

setup(
package_data={
"pytest_codspeed": [
"instruments/hooks/instrument-hooks/includes/*.h",
"instruments/hooks/instrument-hooks/dist/*.c",
]
},
ext_modules=(
[ffi_extension] if IS_EXTENSION_BUILDABLE and not SKIP_EXTENSION_BUILD else []
[native_extension]
if IS_EXTENSION_BUILDABLE and not SKIP_EXTENSION_BUILD
else []
),
)
46 changes: 27 additions & 19 deletions src/pytest_codspeed/instruments/hooks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
import warnings
from typing import TYPE_CHECKING

from pytest_codspeed.utils import SUPPORTS_PERF_TRAMPOLINE

if TYPE_CHECKING:
from .dist_instrument_hooks import InstrumentHooksPointer, LibType
from typing import Any

from pytest_codspeed.utils import SUPPORTS_PERF_TRAMPOLINE


class InstrumentHooks:
"""Zig library wrapper class providing benchmark measurement functionality."""
"""Native library wrapper class providing benchmark measurement functionality."""

lib: LibType
instance: InstrumentHooksPointer
_module: Any
_instance: Any

def __init__(self) -> None:
if os.environ.get("CODSPEED_ENV") is None:
Expand All @@ -25,31 +25,31 @@ def __init__(self) -> None:
)

try:
from .dist_instrument_hooks import lib # type: ignore
from . import dist_instrument_hooks # type: ignore
except ImportError as e:
raise RuntimeError(f"Failed to load instrument hooks library: {e}") from e
self.lib = lib
self._module = dist_instrument_hooks

self.instance = self.lib.instrument_hooks_init()
if self.instance == 0:
self._instance = self._module.instrument_hooks_init()
if self._instance is None:
raise RuntimeError("Failed to initialize CodSpeed instrumentation library.")

if SUPPORTS_PERF_TRAMPOLINE:
sys.activate_stack_trampoline("perf") # type: ignore

def __del__(self):
if hasattr(self, "lib") and hasattr(self, "instance"):
self.lib.instrument_hooks_deinit(self.instance)
# Don't manually deinit - let the capsule destructor handle it
pass

def start_benchmark(self) -> None:
"""Start a new benchmark measurement."""
ret = self.lib.instrument_hooks_start_benchmark(self.instance)
ret = self._module.instrument_hooks_start_benchmark(self._instance)
if ret != 0:
warnings.warn("Failed to start benchmark measurement", RuntimeWarning)

def stop_benchmark(self) -> None:
"""Stop the current benchmark measurement."""
ret = self.lib.instrument_hooks_stop_benchmark(self.instance)
ret = self._module.instrument_hooks_stop_benchmark(self._instance)
if ret != 0:
warnings.warn("Failed to stop benchmark measurement", RuntimeWarning)

Expand All @@ -63,20 +63,28 @@ def set_executed_benchmark(self, uri: str, pid: int | None = None) -> None:
if pid is None:
pid = os.getpid()

ret = self.lib.instrument_hooks_set_executed_benchmark(
self.instance, pid, uri.encode("ascii")
ret = self._module.instrument_hooks_set_executed_benchmark(
self._instance, pid, uri.encode("ascii")
)
if ret != 0:
warnings.warn("Failed to set executed benchmark", RuntimeWarning)

def set_integration(self, name: str, version: str) -> None:
"""Set the integration name and version."""
ret = self.lib.instrument_hooks_set_integration(
self.instance, name.encode("ascii"), version.encode("ascii")
ret = self._module.instrument_hooks_set_integration(
self._instance, name.encode("ascii"), version.encode("ascii")
)
if ret != 0:
warnings.warn("Failed to set integration name and version", RuntimeWarning)

def is_instrumented(self) -> bool:
"""Check if instrumentation is active."""
return self.lib.instrument_hooks_is_instrumented(self.instance)
return self._module.instrument_hooks_is_instrumented(self._instance)

def callgrind_start_instrumentation(self) -> None:
"""Start callgrind instrumentation."""
self._module.callgrind_start_instrumentation()

def callgrind_stop_instrumentation(self) -> None:
"""Stop callgrind instrumentation."""
self._module.callgrind_stop_instrumentation()
51 changes: 0 additions & 51 deletions src/pytest_codspeed/instruments/hooks/build.py

This file was deleted.

Loading