pytest-codspeed 4.3.0__tar.gz → 4.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {pytest_codspeed-4.3.0/src/pytest_codspeed.egg-info → pytest_codspeed-4.4.0}/PKG-INFO +1 -1
  2. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/pyproject.toml +1 -1
  3. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/__init__.py +2 -2
  4. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/analysis.py +1 -0
  5. pytest_codspeed-4.4.0/src/pytest_codspeed/instruments/hooks/__init__.py +197 -0
  6. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/build.py +11 -0
  7. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/dist_instrument_hooks.pyi +18 -0
  8. pytest_codspeed-4.4.0/src/pytest_codspeed/instruments/hooks/instrument-hooks/dist/core.c +30987 -0
  9. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/includes/core.h +30 -15
  10. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/walltime.py +2 -1
  11. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0/src/pytest_codspeed.egg-info}/PKG-INFO +1 -1
  12. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/tests/test_pytest_plugin_walltime.py +56 -1
  13. pytest_codspeed-4.3.0/src/pytest_codspeed/instruments/hooks/__init__.py +0 -94
  14. pytest_codspeed-4.3.0/src/pytest_codspeed/instruments/hooks/instrument-hooks/dist/core.c +0 -11220
  15. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/LICENSE +0 -0
  16. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/README.md +0 -0
  17. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/setup.cfg +0 -0
  18. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/setup.py +0 -0
  19. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/config.py +0 -0
  20. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/__init__.py +0 -0
  21. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/includes/callgrind.h +0 -0
  22. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/includes/compat.h +0 -0
  23. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/includes/valgrind.h +0 -0
  24. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/includes/zig.h +0 -0
  25. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/instruments/hooks/instrument-hooks/scripts/release.py +0 -0
  26. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/plugin.py +0 -0
  27. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/py.typed +0 -0
  28. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed/utils.py +0 -0
  29. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed.egg-info/SOURCES.txt +0 -0
  30. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed.egg-info/dependency_links.txt +0 -0
  31. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed.egg-info/entry_points.txt +0 -0
  32. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed.egg-info/requires.txt +0 -0
  33. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/src/pytest_codspeed.egg-info/top_level.txt +0 -0
  34. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/tests/test_format_time.py +0 -0
  35. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/tests/test_pytest_plugin.py +0 -0
  36. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/tests/test_pytest_plugin_cpu_instrumentation.py +0 -0
  37. {pytest_codspeed-4.3.0 → pytest_codspeed-4.4.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-codspeed
3
- Version: 4.3.0
3
+ Version: 4.4.0
4
4
  Summary: Pytest plugin to create CodSpeed benchmarks
5
5
  Author-email: Arthur Pastel <arthur@codspeed.io>
6
6
  License: The MIT License (MIT)
@@ -77,7 +77,7 @@ version = { attr = "pytest_codspeed.__version__" }
77
77
 
78
78
 
79
79
  [tool.bumpver]
80
- current_version = "4.3.0"
80
+ current_version = "4.4.0"
81
81
  version_pattern = "MAJOR.MINOR.PATCH[-TAG[NUM]]"
82
82
  commit_message = "Release v{new_version} 🚀"
83
83
  tag_message = "Release v{new_version} 🚀"
@@ -1,6 +1,6 @@
1
- __version__ = "4.3.0"
1
+ __version__ = "4.4.0"
2
2
  # We also have the semver version since __version__ is not semver compliant
3
- __semver_version__ = "4.3.0"
3
+ __semver_version__ = "4.4.0"
4
4
 
5
5
  from .plugin import BenchmarkFixture
6
6
 
@@ -33,6 +33,7 @@ class AnalysisInstrument(Instrument):
33
33
  try:
34
34
  self.instrument_hooks = InstrumentHooks()
35
35
  self.instrument_hooks.set_integration("pytest-codspeed", __semver_version__)
36
+ self.instrument_hooks.collect_and_write_python_environment()
36
37
  except RuntimeError as e:
37
38
  if os.environ.get("CODSPEED_ENV") is not None:
38
39
  raise Exception(
@@ -0,0 +1,197 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import platform
5
+ import shlex
6
+ import sys
7
+ import sysconfig
8
+ import warnings
9
+ from typing import TYPE_CHECKING
10
+
11
+ from pytest_codspeed.utils import SUPPORTS_PERF_TRAMPOLINE
12
+
13
+ if TYPE_CHECKING:
14
+ from cffi import FFI
15
+
16
+ from .dist_instrument_hooks import InstrumentHooksPointer, LibType
17
+
18
+ # Feature flags for instrument hooks
19
+ FEATURE_DISABLE_CALLGRIND_MARKERS = 0
20
+
21
+
22
+ class InstrumentHooks:
23
+ """Zig library wrapper class providing benchmark measurement functionality."""
24
+
25
+ lib: LibType
26
+ ffi: FFI
27
+ instance: InstrumentHooksPointer
28
+
29
+ def __init__(self) -> None:
30
+ if os.environ.get("CODSPEED_ENV") is None:
31
+ raise RuntimeError(
32
+ "Can't run benchmarks outside of CodSpeed environment."
33
+ "Please set the CODSPEED_ENV environment variable."
34
+ )
35
+
36
+ try:
37
+ from .dist_instrument_hooks import ffi, lib # type: ignore
38
+ except ImportError as e:
39
+ raise RuntimeError(f"Failed to load instrument hooks library: {e}") from e
40
+ self.lib = lib
41
+ self.ffi = ffi
42
+
43
+ self.instance = self.lib.instrument_hooks_init()
44
+ if self.instance == 0:
45
+ raise RuntimeError("Failed to initialize CodSpeed instrumentation library.")
46
+
47
+ if SUPPORTS_PERF_TRAMPOLINE and not sys.is_stack_trampoline_active():
48
+ sys.activate_stack_trampoline("perf") # type: ignore
49
+
50
+ def __del__(self):
51
+ if hasattr(self, "lib") and hasattr(self, "instance"):
52
+ self.lib.instrument_hooks_deinit(self.instance)
53
+
54
+ def start_benchmark(self) -> None:
55
+ """Start a new benchmark measurement."""
56
+ ret = self.lib.instrument_hooks_start_benchmark(self.instance)
57
+ if ret != 0:
58
+ warnings.warn("Failed to start benchmark measurement", RuntimeWarning)
59
+
60
+ def stop_benchmark(self) -> None:
61
+ """Stop the current benchmark measurement."""
62
+ ret = self.lib.instrument_hooks_stop_benchmark(self.instance)
63
+ if ret != 0:
64
+ warnings.warn("Failed to stop benchmark measurement", RuntimeWarning)
65
+
66
+ def set_executed_benchmark(self, uri: str, pid: int | None = None) -> None:
67
+ """Set the executed benchmark URI and process ID.
68
+
69
+ Args:
70
+ uri: The benchmark URI string identifier
71
+ pid: Optional process ID (defaults to current process)
72
+ """
73
+ if pid is None:
74
+ pid = os.getpid()
75
+
76
+ ret = self.lib.instrument_hooks_set_executed_benchmark(
77
+ self.instance, pid, uri.encode("ascii")
78
+ )
79
+ if ret != 0:
80
+ warnings.warn("Failed to set executed benchmark", RuntimeWarning)
81
+
82
+ def set_integration(self, name: str, version: str) -> None:
83
+ """Set the integration name and version."""
84
+ ret = self.lib.instrument_hooks_set_integration(
85
+ self.instance, name.encode("ascii"), version.encode("ascii")
86
+ )
87
+ if ret != 0:
88
+ warnings.warn("Failed to set integration name and version", RuntimeWarning)
89
+
90
+ def is_instrumented(self) -> bool:
91
+ """Check if simulation is active."""
92
+ return self.lib.instrument_hooks_is_instrumented(self.instance)
93
+
94
+ def set_feature(self, feature: int, enabled: bool) -> None:
95
+ """Set a feature flag in the instrument hooks library.
96
+
97
+ Args:
98
+ feature: The feature flag to set
99
+ enabled: Whether to enable or disable the feature
100
+ """
101
+ self.lib.instrument_hooks_set_feature(feature, enabled)
102
+
103
+ def set_environment(self, section_name: str, key: str, value: str) -> None:
104
+ """Register a key-value pair under a named section for environment collection.
105
+
106
+ Args:
107
+ section_name: The section name (e.g. "Python")
108
+ key: The key (e.g. "version")
109
+ value: The value (e.g. "3.13.12")
110
+ """
111
+ ret = self.lib.instrument_hooks_set_environment(
112
+ self.instance,
113
+ section_name.encode("utf-8"),
114
+ key.encode("utf-8"),
115
+ value.encode("utf-8"),
116
+ )
117
+ if ret != 0:
118
+ warnings.warn("Failed to set environment data", RuntimeWarning)
119
+
120
+ def set_environment_list(
121
+ self, section_name: str, key: str, values: list[str]
122
+ ) -> None:
123
+ """Register a list of values under a named section for environment collection.
124
+
125
+ Args:
126
+ section_name: The section name (e.g. "python")
127
+ key: The key (e.g. "build_args")
128
+ values: The list of string values
129
+ """
130
+ encoded = [self.ffi.new("char[]", v.encode("utf-8")) for v in values]
131
+ c_values = self.ffi.new("char*[]", encoded)
132
+ ret = self.lib.instrument_hooks_set_environment_list(
133
+ self.instance,
134
+ section_name.encode("utf-8"),
135
+ key.encode("utf-8"),
136
+ c_values,
137
+ len(encoded),
138
+ )
139
+ if ret != 0:
140
+ warnings.warn("Failed to set environment list data", RuntimeWarning)
141
+
142
+ def write_environment(self, pid: int | None = None) -> None:
143
+ """Flush all registered environment sections to disk.
144
+
145
+ Writes to $CODSPEED_PROFILE_FOLDER/environment-<pid>.json.
146
+
147
+ Args:
148
+ pid: Optional process ID (defaults to current process)
149
+ """
150
+ if pid is None:
151
+ pid = os.getpid()
152
+ ret = self.lib.instrument_hooks_write_environment(self.instance, pid)
153
+ if ret != 0:
154
+ warnings.warn("Failed to write environment data", RuntimeWarning)
155
+
156
+ def collect_and_write_python_environment(self) -> None:
157
+ """Collect Python toolchain information and write it to disk."""
158
+ section = "python"
159
+ set_env = self.set_environment
160
+
161
+ # Core identity
162
+ set_env(section, "version", sys.version.strip())
163
+ set_env(section, "implementation", sys.implementation.name.strip())
164
+ set_env(section, "compiler", platform.python_compiler().strip())
165
+
166
+ config_vars = sysconfig.get_config_vars()
167
+
168
+ # Build arguments as a list
169
+ config_args = config_vars.get("CONFIG_ARGS", "")
170
+ if config_args:
171
+ build_args = [arg.strip() for arg in shlex.split(config_args)]
172
+ self.set_environment_list(section, "build_args", build_args)
173
+
174
+ # Performance-relevant build configuration as "KEY=value" list
175
+ _SYSCONFIG_KEYS = (
176
+ "abiflags",
177
+ "PY_ENABLE_SHARED",
178
+ "Py_GIL_DISABLED",
179
+ "Py_DEBUG",
180
+ "WITH_PYMALLOC",
181
+ "WITH_MIMALLOC",
182
+ "WITH_FREELISTS",
183
+ "HAVE_COMPUTED_GOTOS",
184
+ "Py_STATS",
185
+ "Py_TRACE_REFS",
186
+ "WITH_VALGRIND",
187
+ "WITH_DTRACE",
188
+ )
189
+ config_items = []
190
+ for key in _SYSCONFIG_KEYS:
191
+ value = config_vars.get(key)
192
+ if value is not None:
193
+ config_items.append(f"{key}={str(value).strip()}")
194
+ config_items.append(f"perf_trampoline={SUPPORTS_PERF_TRAMPOLINE}")
195
+ self.set_environment_list(section, "config", config_items)
196
+
197
+ self.write_environment()
@@ -36,6 +36,15 @@ void callgrind_start_instrumentation();
36
36
  void callgrind_stop_instrumentation();
37
37
 
38
38
  void instrument_hooks_set_feature(uint64_t feature, bool enabled);
39
+
40
+ uint8_t instrument_hooks_set_environment(InstrumentHooks *, const char *section_name,
41
+ const char *key, const char *value);
42
+ uint8_t instrument_hooks_set_environment_list(InstrumentHooks *,
43
+ const char *section_name,
44
+ const char *key,
45
+ const char *const *values,
46
+ uint32_t count);
47
+ uint8_t instrument_hooks_write_environment(InstrumentHooks *, uint32_t pid);
39
48
  """)
40
49
 
41
50
  ffibuilder.set_source(
@@ -47,6 +56,8 @@ ffibuilder.set_source(
47
56
  "src/pytest_codspeed/instruments/hooks/instrument-hooks/dist/core.c",
48
57
  ],
49
58
  include_dirs=[str(includes_dir)],
59
+ # IMPORTANT: Keep in sync with instrument-hooks/ci.yml (COMMON_CFLAGS)
60
+ extra_compile_args=["-Wno-format-security"],
50
61
  )
51
62
 
52
63
  if __name__ == "__main__":
@@ -1,3 +1,5 @@
1
+ from cffi import FFI
2
+
1
3
  InstrumentHooksPointer = object
2
4
 
3
5
  class lib:
@@ -31,5 +33,21 @@ class lib:
31
33
  def callgrind_stop_instrumentation() -> int: ...
32
34
  @staticmethod
33
35
  def instrument_hooks_set_feature(feature: int, enabled: bool) -> None: ...
36
+ @staticmethod
37
+ def instrument_hooks_set_environment(
38
+ hooks: InstrumentHooksPointer, section_name: bytes, key: bytes, value: bytes
39
+ ) -> int: ...
40
+ @staticmethod
41
+ def instrument_hooks_set_environment_list(
42
+ hooks: InstrumentHooksPointer,
43
+ section_name: bytes,
44
+ key: bytes,
45
+ values: FFI.CData,
46
+ count: int,
47
+ ) -> int: ...
48
+ @staticmethod
49
+ def instrument_hooks_write_environment(
50
+ hooks: InstrumentHooksPointer, pid: int
51
+ ) -> int: ...
34
52
 
35
53
  LibType = type[lib]