Skip to content

Commit 245b872

Browse files
committed
feat: add instrument-hooks native module
1 parent c92304c commit 245b872

File tree

6 files changed

+114
-5
lines changed

6 files changed

+114
-5
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ jobs:
1515
runs-on: ubuntu-latest
1616
steps:
1717
- uses: actions/checkout@v4
18+
with:
19+
submodules: true
1820
- name: Set up Python 3.11
1921
uses: actions/setup-python@v5
2022
with:
@@ -44,6 +46,8 @@ jobs:
4446

4547
steps:
4648
- uses: actions/checkout@v4
49+
with:
50+
submodules: true
4751
- uses: astral-sh/setup-uv@v4
4852
with:
4953
version: "0.5.20"

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[submodule "tests/benchmarks/TheAlgorithms"]
22
path = tests/benchmarks/TheAlgorithms
33
url = [email protected]:TheAlgorithms/Python.git
4+
[submodule "src/pytest_codspeed/instruments/hooks/instrument-hooks"]
5+
path = src/pytest_codspeed/instruments/hooks/instrument-hooks
6+
url = https://github.com/CodSpeedHQ/instrument-hooks

setup.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55

66
from setuptools import setup
77

8-
build_path = (
9-
Path(__file__).parent / "src/pytest_codspeed/instruments/valgrind/_wrapper/build.py"
10-
)
8+
build_path = Path(__file__).parent / "src/pytest_codspeed/instruments/hooks/build.py"
119

1210
spec = importlib.util.spec_from_file_location("build", build_path)
1311
assert spec is not None, "The spec should be initialized"
@@ -52,8 +50,8 @@
5250
setup(
5351
package_data={
5452
"pytest_codspeed": [
55-
"instruments/valgrind/_wrapper/*.h",
56-
"instruments/valgrind/_wrapper/*.c",
53+
"instruments/hooks/instrument-hooks/includes/*.h",
54+
"instruments/hooks/instrument-hooks/dist/*.c",
5755
]
5856
},
5957
ext_modules=(
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from __future__ import annotations
2+
3+
import os
4+
import sys
5+
from typing import TYPE_CHECKING
6+
7+
if TYPE_CHECKING:
8+
from .dist_instrument_hooks import lib as LibType
9+
10+
SUPPORTS_PERF_TRAMPOLINE = sys.version_info >= (3, 12)
11+
12+
13+
class InstrumentHooks:
14+
"""Zig library wrapper class providing benchmark measurement functionality."""
15+
16+
lib: LibType
17+
instance: int
18+
19+
def __init__(self) -> None:
20+
if not os.environ.get("CODSPEED_ENV") is not None:
21+
raise RuntimeError(
22+
"Can't run benchmarks outside of CodSpeed environment."
23+
"Please set the CODSPEED_ENV environment variable."
24+
)
25+
26+
try:
27+
from .dist_instrument_hooks import lib # type: ignore
28+
except ImportError as e:
29+
raise RuntimeError("Failed to load instrument hooks library") from e
30+
31+
instance = lib.instrument_hooks_init()
32+
if instance == 0:
33+
raise RuntimeError("Failed to initialize CodSpeed instrumentation library.")
34+
35+
if SUPPORTS_PERF_TRAMPOLINE:
36+
sys.activate_stack_trampoline("perf") # type: ignore
37+
38+
self.lib = lib
39+
self.instance = instance
40+
41+
def __del__(self):
42+
if hasattr(self, "lib") and hasattr(self, "instance"):
43+
self.lib.instrument_hooks_deinit(self.instance)
44+
45+
def start_benchmark(self) -> None:
46+
"""Start a new benchmark measurement."""
47+
self.lib.instrument_hooks_start_benchmark(self.instance)
48+
49+
def stop_benchmark(self) -> None:
50+
"""Stop the current benchmark measurement."""
51+
self.lib.instrument_hooks_stop_benchmark(self.instance)
52+
53+
def set_executed_benchmark(self, uri: str, pid: int | None = None) -> None:
54+
"""Set the executed benchmark URI and process ID.
55+
56+
Args:
57+
uri: The benchmark URI string identifier
58+
pid: Optional process ID (defaults to current process)
59+
"""
60+
if pid is None:
61+
pid = os.getpid()
62+
self.lib.instrument_hooks_executed_benchmark(
63+
self.instance, pid, uri.encode("ascii")
64+
)
65+
66+
def set_integration(self, name: str, version: str) -> None:
67+
"""Set the integration name and version."""
68+
self.lib.instrument_hooks_set_integration(
69+
self.instance, name.encode("ascii"), version.encode("ascii")
70+
)
71+
72+
def is_instrumented(self) -> bool:
73+
"""Check if instrumentation is active."""
74+
return self.lib.instrument_hooks_is_instrumented(self.instance)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from pathlib import Path
2+
3+
from cffi import FFI # type: ignore
4+
5+
ffibuilder = FFI()
6+
7+
includes_dir = Path(__file__).parent.joinpath("instrument-hooks/includes")
8+
header_text = (includes_dir / "core.h").read_text()
9+
filtered_header = "\n".join(
10+
line for line in header_text.splitlines() if not line.strip().startswith("#")
11+
)
12+
ffibuilder.cdef(filtered_header)
13+
14+
ffibuilder.set_source(
15+
"pytest_codspeed.instruments.hooks.dist_instrument_hooks",
16+
"""
17+
#include "core.h"
18+
#include <quadmath.h>
19+
""",
20+
libraries=["m", "quadmath"],
21+
sources=[
22+
"src/pytest_codspeed/instruments/hooks/instrument-hooks/dist/core.c",
23+
],
24+
include_dirs=[str(includes_dir)],
25+
extra_compile_args=["-lm", "-lcs50"],
26+
)
27+
28+
if __name__ == "__main__":
29+
ffibuilder.compile(verbose=True)

0 commit comments

Comments
 (0)