Skip to content

Commit d1cc3ff

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

6 files changed

Lines changed: 120 additions & 5 deletions

File tree

.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 = git@github.com: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: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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 os.environ.get("CODSPEED_ENV") is 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(f"Failed to load instrument hooks library: {e}") 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+
ret = self.lib.instrument_hooks_start_benchmark(self.instance)
48+
if ret != 0:
49+
raise RuntimeError("Failed to start benchmark measurement")
50+
51+
def stop_benchmark(self) -> None:
52+
"""Stop the current benchmark measurement."""
53+
ret = self.lib.instrument_hooks_stop_benchmark(self.instance)
54+
if ret != 0:
55+
raise RuntimeError("Failed to stop benchmark measurement")
56+
57+
def set_executed_benchmark(self, uri: str, pid: int | None = None) -> None:
58+
"""Set the executed benchmark URI and process ID.
59+
60+
Args:
61+
uri: The benchmark URI string identifier
62+
pid: Optional process ID (defaults to current process)
63+
"""
64+
if pid is None:
65+
pid = os.getpid()
66+
67+
ret = self.lib.instrument_hooks_executed_benchmark(
68+
self.instance, pid, uri.encode("ascii")
69+
)
70+
if ret != 0:
71+
raise RuntimeError("Failed to set executed benchmark")
72+
73+
def set_integration(self, name: str, version: str) -> None:
74+
"""Set the integration name and version."""
75+
ret = self.lib.instrument_hooks_set_integration(
76+
self.instance, name.encode("ascii"), version.encode("ascii")
77+
)
78+
if ret != 0:
79+
raise RuntimeError("Failed to set integration name and version")
80+
81+
def is_instrumented(self) -> bool:
82+
"""Check if instrumentation is active."""
83+
return self.lib.instrument_hooks_is_instrumented(self.instance)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
""",
19+
sources=[
20+
"src/pytest_codspeed/instruments/hooks/instrument-hooks/dist/core.c",
21+
],
22+
include_dirs=[str(includes_dir)],
23+
)
24+
25+
if __name__ == "__main__":
26+
ffibuilder.compile(verbose=True)
Submodule instrument-hooks added at b003e50

0 commit comments

Comments
 (0)