regscope 0.1.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 (53) hide show
  1. regscope-0.1.0/PKG-INFO +214 -0
  2. regscope-0.1.0/README.md +179 -0
  3. regscope-0.1.0/pyproject.toml +53 -0
  4. regscope-0.1.0/regscope/__init__.py +6 -0
  5. regscope-0.1.0/regscope/__main__.py +5 -0
  6. regscope-0.1.0/regscope/api/__init__.py +6 -0
  7. regscope-0.1.0/regscope/api/collectors.py +50 -0
  8. regscope-0.1.0/regscope/api/config.py +27 -0
  9. regscope-0.1.0/regscope/api/decorators.py +227 -0
  10. regscope-0.1.0/regscope/cli/__init__.py +5 -0
  11. regscope-0.1.0/regscope/cli/main.py +80 -0
  12. regscope-0.1.0/regscope/cli/trend.py +22 -0
  13. regscope-0.1.0/regscope/collectors/__init__.py +14 -0
  14. regscope-0.1.0/regscope/collectors/call_graph.py +93 -0
  15. regscope-0.1.0/regscope/collectors/http.py +76 -0
  16. regscope-0.1.0/regscope/collectors/memory.py +41 -0
  17. regscope-0.1.0/regscope/collectors/redis.py +76 -0
  18. regscope-0.1.0/regscope/collectors/sqlalchemy.py +57 -0
  19. regscope-0.1.0/regscope/core/__init__.py +1 -0
  20. regscope-0.1.0/regscope/core/comparison.py +99 -0
  21. regscope-0.1.0/regscope/core/runtime.py +49 -0
  22. regscope-0.1.0/regscope/errors.py +9 -0
  23. regscope-0.1.0/regscope/models.py +112 -0
  24. regscope-0.1.0/regscope/py.typed +0 -0
  25. regscope-0.1.0/regscope/pytest_plugin.py +74 -0
  26. regscope-0.1.0/regscope/storage/__init__.py +5 -0
  27. regscope-0.1.0/regscope/storage/json_store.py +121 -0
  28. regscope-0.1.0/regscope/trends/__init__.py +5 -0
  29. regscope-0.1.0/regscope/trends/history.py +137 -0
  30. regscope-0.1.0/regscope.egg-info/PKG-INFO +214 -0
  31. regscope-0.1.0/regscope.egg-info/SOURCES.txt +51 -0
  32. regscope-0.1.0/regscope.egg-info/dependency_links.txt +1 -0
  33. regscope-0.1.0/regscope.egg-info/entry_points.txt +5 -0
  34. regscope-0.1.0/regscope.egg-info/requires.txt +18 -0
  35. regscope-0.1.0/regscope.egg-info/top_level.txt +1 -0
  36. regscope-0.1.0/setup.cfg +4 -0
  37. regscope-0.1.0/tests/test_async_tracking.py +52 -0
  38. regscope-0.1.0/tests/test_call_graph.py +127 -0
  39. regscope-0.1.0/tests/test_ci_baseline.py +8 -0
  40. regscope-0.1.0/tests/test_cli.py +65 -0
  41. regscope-0.1.0/tests/test_comparison.py +69 -0
  42. regscope-0.1.0/tests/test_decorators.py +150 -0
  43. regscope-0.1.0/tests/test_history.py +89 -0
  44. regscope-0.1.0/tests/test_http_collector.py +17 -0
  45. regscope-0.1.0/tests/test_memory_collector.py +25 -0
  46. regscope-0.1.0/tests/test_models.py +75 -0
  47. regscope-0.1.0/tests/test_optional_integrations.py +182 -0
  48. regscope-0.1.0/tests/test_package.py +5 -0
  49. regscope-0.1.0/tests/test_pytest_plugin.py +95 -0
  50. regscope-0.1.0/tests/test_redis_collector.py +17 -0
  51. regscope-0.1.0/tests/test_runtime.py +30 -0
  52. regscope-0.1.0/tests/test_sqlalchemy_collector.py +55 -0
  53. regscope-0.1.0/tests/test_storage.py +98 -0
@@ -0,0 +1,214 @@
1
+ Metadata-Version: 2.4
2
+ Name: regscope
3
+ Version: 0.1.0
4
+ Summary: Observed behavioral diffing for Python functions
5
+ Author: RegScope contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/AdeelMalik22/regscope
8
+ Project-URL: Repository, https://github.com/AdeelMalik22/regscope
9
+ Project-URL: Issues, https://github.com/AdeelMalik22/regscope/issues
10
+ Keywords: profiling,regression-testing,performance,observability
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Testing
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ Provides-Extra: dev
23
+ Requires-Dist: build; extra == "dev"
24
+ Requires-Dist: pytest; extra == "dev"
25
+ Provides-Extra: db
26
+ Requires-Dist: SQLAlchemy>=1.4; extra == "db"
27
+ Provides-Extra: http
28
+ Requires-Dist: requests>=2.28; extra == "http"
29
+ Provides-Extra: redis
30
+ Requires-Dist: redis>=4.0; extra == "redis"
31
+ Provides-Extra: integration
32
+ Requires-Dist: SQLAlchemy>=1.4; extra == "integration"
33
+ Requires-Dist: requests>=2.28; extra == "integration"
34
+ Requires-Dist: redis>=4.0; extra == "integration"
35
+
36
+ # RegScope
37
+
38
+ RegScope is a behavioral diffing library for Python functions. It records what
39
+ a function did during a controlled test run so behavior can be compared across
40
+ code versions.
41
+
42
+ RegScope does not prove that two functions are mathematically equivalent. It
43
+ reports differences in behavior observed during the runs that were recorded.
44
+
45
+ ## In plain language
46
+
47
+ RegScope is like a before-and-after checkup for a Python function. You place
48
+ `@track` above a function, run your normal tests, and RegScope quietly records
49
+ useful facts about that run: how long the function took, whether it failed,
50
+ which Python functions it called, and—when configured—how many database,
51
+ HTTP, or Redis operations it made.
52
+
53
+ When the function runs again after a code change, RegScope compares the new
54
+ checkup with earlier runs. It can warn you that a function became slower,
55
+ started raising errors, made more database queries, or used more memory. In
56
+ CI, that warning can fail the build so a performance or behavior regression is
57
+ noticed before the change is released.
58
+
59
+ It does not change what your function returns, and it does not decide whether
60
+ two implementations are mathematically identical. It compares what happened
61
+ during the test cases you actually ran. It also does not save function
62
+ arguments, return values, SQL text, URLs, request bodies, Redis keys, or Redis
63
+ values.
64
+
65
+ ## Current status
66
+
67
+ The early v0.1 core supports:
68
+
69
+ - Synchronous and asynchronous `@track` decoration
70
+ - Execution duration measurement
71
+ - Exception counting while preserving the original exception
72
+ - Flat call-graph counts using `sys.setprofile()`
73
+ - Structured JSON behavior profiles
74
+ - N-run JSON baselines with atomic file replacement
75
+ - Configurable baseline storage through `baseline_dir`
76
+ - Zero required runtime dependencies
77
+
78
+ ## Quick start
79
+
80
+ ```python
81
+ from regscope import track
82
+
83
+
84
+ @track
85
+ def calculate_total(values: list[int]) -> int:
86
+ return sum(values)
87
+
88
+
89
+ total = calculate_total([1, 2, 3])
90
+ profile = calculate_total.last_profile
91
+ print(total)
92
+ print(profile.duration_ns)
93
+ ```
94
+
95
+ Use `warmup_runs=N` to execute and report the first N calls without adding
96
+ them to the baseline or historical trend. This is useful when the first call
97
+ opens connections, imports modules, or initializes caches:
98
+
99
+ ```python
100
+ @track(baseline_dir=".regscope", warmup_runs=1)
101
+ def load_dashboard() -> int:
102
+ return 42
103
+ ```
104
+
105
+ The decorator supports both `@track` and `@track(...)`. Async functions are
106
+ supported transparently:
107
+
108
+ ```python
109
+ from regscope import track
110
+
111
+
112
+ @track
113
+ async def fetch_value() -> int:
114
+ return 42
115
+ ```
116
+
117
+ After a call, `function.get_current_profile()` and
118
+ `function.get_current_comparison()` return values stored in the current
119
+ `contextvars` context. These accessors are task-safe for concurrent async
120
+ invocations. The legacy `function.last_profile` and
121
+ `function.last_comparison` attributes remain available as compatibility
122
+ snapshots, but can be overwritten by another concurrent invocation.
123
+
124
+ Each tracked function writes a bounded JSON baseline to the configured
125
+ directory. The default directory is `.regscope`. Profiles contain structured
126
+ metrics and call counts; they do not capture function arguments or sensitive
127
+ external data.
128
+
129
+ ## CI regression checks
130
+
131
+ The repository workflow keeps trusted baselines outside Git. A push to
132
+ `master` runs `tests/ci_targets`, records the baseline in `.regscope`, and
133
+ uploads it as the `regscope-baseline-master` artifact. Pull-request jobs
134
+ download the latest successful artifact from `master` and compare the same
135
+ targets against it. A comparison that exceeds the configured threshold fails
136
+ the job.
137
+
138
+ Baseline artifacts are retained for 30 days. The workflow includes hidden
139
+ files when uploading because `.regscope` is a dot-directory. The baseline
140
+ publisher is restricted to trusted `master` pushes; pull requests cannot
141
+ replace the trusted artifact, including pull requests from forks.
142
+
143
+ To reproduce the comparison locally, first generate a trusted baseline and
144
+ then run the targets without the update flag:
145
+
146
+ ```bash
147
+ REGSCOPE_CI_BASELINE=1 REGSCOPE_TRUSTED_BASELINE=1 \
148
+ .venv/bin/python -m pytest tests/ci_targets \
149
+ --regscope-baseline-dir .regscope --regscope-update-baseline
150
+
151
+ REGSCOPE_CI_BASELINE=1 .venv/bin/python -m pytest tests/ci_targets \
152
+ --regscope-baseline-dir .regscope
153
+ ```
154
+
155
+ If no artifact has been published yet, the pull-request job reports that the
156
+ trusted baseline is unavailable and the comparison target fails rather than
157
+ silently treating the missing baseline as a pass.
158
+
159
+ ## Limitations
160
+
161
+ - `sys.setprofile()` has one active profiler per current thread. RegScope
162
+ restores the profiler observed at entry, and independent threads have
163
+ independent collection contexts. RegScope does not arbitrate nested owners
164
+ in one thread or an external profiler that replaces its hook during an
165
+ execution. Run tracked profiling in an isolated test context when coverage,
166
+ a debugger, or another profiler must remain active.
167
+ - HTTP and Redis collectors temporarily replace process-global library hooks.
168
+ They restore the hook that was present when attached, are thread-safe for
169
+ counting calls, and leave a newer hook installed by another tool untouched.
170
+ Only one RegScope collector may own each library hook at a time; a
171
+ conflicting attachment fails clearly instead of stacking wrappers.
172
+ - Profiles describe observed executions, not all possible behavior.
173
+ - The CI artifact workflow is validated on trusted `master` runs; a real
174
+ pull-request event is still required to exercise GitHub's fork permissions
175
+ and artifact-download path end to end.
176
+
177
+ ## Development
178
+
179
+ Install development dependencies and run the tests:
180
+
181
+ ```bash
182
+ .venv/bin/python -m pip install ".[dev]"
183
+ .venv/bin/python -m pytest
184
+ ```
185
+
186
+ The implementation roadmap is in [IMPLEMENTATION_PLAN.md](IMPLEMENTATION_PLAN.md).
187
+ Release history is in [CHANGELOG.md](CHANGELOG.md), with the release policy in
188
+ [VERSIONING.md](VERSIONING.md).
189
+ Performance and threshold guidance is in [docs/performance.md](docs/performance.md).
190
+
191
+ ## Privacy and measurements
192
+
193
+ Profiles contain aggregate timing, call, exception, collector, and memory
194
+ metrics. RegScope does not capture function arguments, return values, SQL
195
+ text, bound parameters, URLs, headers, request bodies, Redis keys, or Redis
196
+ values. The SHA-256 fingerprint is derived from the structured profile and is
197
+ not a substitute for the profile itself.
198
+
199
+ The core call profiler adds measurable overhead, especially for functions with
200
+ large call graphs. Run the local benchmark with:
201
+
202
+ ```bash
203
+ .venv/bin/python -m benchmarks.overhead
204
+ ```
205
+
206
+ Benchmark results depend on the machine and Python version. Use them to tune
207
+ thresholds for a project rather than treating the sample output as universal.
208
+
209
+ ## Schema stability
210
+
211
+ Structured profiles, baselines, and historical trend points currently use
212
+ schema version `1`. Older records that omit a schema field are read as version
213
+ 1. Records from a newer unsupported schema are rejected explicitly so they
214
+ cannot be silently misinterpreted.
@@ -0,0 +1,179 @@
1
+ # RegScope
2
+
3
+ RegScope is a behavioral diffing library for Python functions. It records what
4
+ a function did during a controlled test run so behavior can be compared across
5
+ code versions.
6
+
7
+ RegScope does not prove that two functions are mathematically equivalent. It
8
+ reports differences in behavior observed during the runs that were recorded.
9
+
10
+ ## In plain language
11
+
12
+ RegScope is like a before-and-after checkup for a Python function. You place
13
+ `@track` above a function, run your normal tests, and RegScope quietly records
14
+ useful facts about that run: how long the function took, whether it failed,
15
+ which Python functions it called, and—when configured—how many database,
16
+ HTTP, or Redis operations it made.
17
+
18
+ When the function runs again after a code change, RegScope compares the new
19
+ checkup with earlier runs. It can warn you that a function became slower,
20
+ started raising errors, made more database queries, or used more memory. In
21
+ CI, that warning can fail the build so a performance or behavior regression is
22
+ noticed before the change is released.
23
+
24
+ It does not change what your function returns, and it does not decide whether
25
+ two implementations are mathematically identical. It compares what happened
26
+ during the test cases you actually ran. It also does not save function
27
+ arguments, return values, SQL text, URLs, request bodies, Redis keys, or Redis
28
+ values.
29
+
30
+ ## Current status
31
+
32
+ The early v0.1 core supports:
33
+
34
+ - Synchronous and asynchronous `@track` decoration
35
+ - Execution duration measurement
36
+ - Exception counting while preserving the original exception
37
+ - Flat call-graph counts using `sys.setprofile()`
38
+ - Structured JSON behavior profiles
39
+ - N-run JSON baselines with atomic file replacement
40
+ - Configurable baseline storage through `baseline_dir`
41
+ - Zero required runtime dependencies
42
+
43
+ ## Quick start
44
+
45
+ ```python
46
+ from regscope import track
47
+
48
+
49
+ @track
50
+ def calculate_total(values: list[int]) -> int:
51
+ return sum(values)
52
+
53
+
54
+ total = calculate_total([1, 2, 3])
55
+ profile = calculate_total.last_profile
56
+ print(total)
57
+ print(profile.duration_ns)
58
+ ```
59
+
60
+ Use `warmup_runs=N` to execute and report the first N calls without adding
61
+ them to the baseline or historical trend. This is useful when the first call
62
+ opens connections, imports modules, or initializes caches:
63
+
64
+ ```python
65
+ @track(baseline_dir=".regscope", warmup_runs=1)
66
+ def load_dashboard() -> int:
67
+ return 42
68
+ ```
69
+
70
+ The decorator supports both `@track` and `@track(...)`. Async functions are
71
+ supported transparently:
72
+
73
+ ```python
74
+ from regscope import track
75
+
76
+
77
+ @track
78
+ async def fetch_value() -> int:
79
+ return 42
80
+ ```
81
+
82
+ After a call, `function.get_current_profile()` and
83
+ `function.get_current_comparison()` return values stored in the current
84
+ `contextvars` context. These accessors are task-safe for concurrent async
85
+ invocations. The legacy `function.last_profile` and
86
+ `function.last_comparison` attributes remain available as compatibility
87
+ snapshots, but can be overwritten by another concurrent invocation.
88
+
89
+ Each tracked function writes a bounded JSON baseline to the configured
90
+ directory. The default directory is `.regscope`. Profiles contain structured
91
+ metrics and call counts; they do not capture function arguments or sensitive
92
+ external data.
93
+
94
+ ## CI regression checks
95
+
96
+ The repository workflow keeps trusted baselines outside Git. A push to
97
+ `master` runs `tests/ci_targets`, records the baseline in `.regscope`, and
98
+ uploads it as the `regscope-baseline-master` artifact. Pull-request jobs
99
+ download the latest successful artifact from `master` and compare the same
100
+ targets against it. A comparison that exceeds the configured threshold fails
101
+ the job.
102
+
103
+ Baseline artifacts are retained for 30 days. The workflow includes hidden
104
+ files when uploading because `.regscope` is a dot-directory. The baseline
105
+ publisher is restricted to trusted `master` pushes; pull requests cannot
106
+ replace the trusted artifact, including pull requests from forks.
107
+
108
+ To reproduce the comparison locally, first generate a trusted baseline and
109
+ then run the targets without the update flag:
110
+
111
+ ```bash
112
+ REGSCOPE_CI_BASELINE=1 REGSCOPE_TRUSTED_BASELINE=1 \
113
+ .venv/bin/python -m pytest tests/ci_targets \
114
+ --regscope-baseline-dir .regscope --regscope-update-baseline
115
+
116
+ REGSCOPE_CI_BASELINE=1 .venv/bin/python -m pytest tests/ci_targets \
117
+ --regscope-baseline-dir .regscope
118
+ ```
119
+
120
+ If no artifact has been published yet, the pull-request job reports that the
121
+ trusted baseline is unavailable and the comparison target fails rather than
122
+ silently treating the missing baseline as a pass.
123
+
124
+ ## Limitations
125
+
126
+ - `sys.setprofile()` has one active profiler per current thread. RegScope
127
+ restores the profiler observed at entry, and independent threads have
128
+ independent collection contexts. RegScope does not arbitrate nested owners
129
+ in one thread or an external profiler that replaces its hook during an
130
+ execution. Run tracked profiling in an isolated test context when coverage,
131
+ a debugger, or another profiler must remain active.
132
+ - HTTP and Redis collectors temporarily replace process-global library hooks.
133
+ They restore the hook that was present when attached, are thread-safe for
134
+ counting calls, and leave a newer hook installed by another tool untouched.
135
+ Only one RegScope collector may own each library hook at a time; a
136
+ conflicting attachment fails clearly instead of stacking wrappers.
137
+ - Profiles describe observed executions, not all possible behavior.
138
+ - The CI artifact workflow is validated on trusted `master` runs; a real
139
+ pull-request event is still required to exercise GitHub's fork permissions
140
+ and artifact-download path end to end.
141
+
142
+ ## Development
143
+
144
+ Install development dependencies and run the tests:
145
+
146
+ ```bash
147
+ .venv/bin/python -m pip install ".[dev]"
148
+ .venv/bin/python -m pytest
149
+ ```
150
+
151
+ The implementation roadmap is in [IMPLEMENTATION_PLAN.md](IMPLEMENTATION_PLAN.md).
152
+ Release history is in [CHANGELOG.md](CHANGELOG.md), with the release policy in
153
+ [VERSIONING.md](VERSIONING.md).
154
+ Performance and threshold guidance is in [docs/performance.md](docs/performance.md).
155
+
156
+ ## Privacy and measurements
157
+
158
+ Profiles contain aggregate timing, call, exception, collector, and memory
159
+ metrics. RegScope does not capture function arguments, return values, SQL
160
+ text, bound parameters, URLs, headers, request bodies, Redis keys, or Redis
161
+ values. The SHA-256 fingerprint is derived from the structured profile and is
162
+ not a substitute for the profile itself.
163
+
164
+ The core call profiler adds measurable overhead, especially for functions with
165
+ large call graphs. Run the local benchmark with:
166
+
167
+ ```bash
168
+ .venv/bin/python -m benchmarks.overhead
169
+ ```
170
+
171
+ Benchmark results depend on the machine and Python version. Use them to tune
172
+ thresholds for a project rather than treating the sample output as universal.
173
+
174
+ ## Schema stability
175
+
176
+ Structured profiles, baselines, and historical trend points currently use
177
+ schema version `1`. Older records that omit a schema field are read as version
178
+ 1. Records from a newer unsupported schema are rejected explicitly so they
179
+ cannot be silently misinterpreted.
@@ -0,0 +1,53 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "regscope"
7
+ version = "0.1.0"
8
+ description = "Observed behavioral diffing for Python functions"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ authors = [{ name = "RegScope contributors" }]
13
+ dependencies = []
14
+ keywords = ["profiling", "regression-testing", "performance", "observability"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Topic :: Software Development :: Testing",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/AdeelMalik22/regscope"
29
+ Repository = "https://github.com/AdeelMalik22/regscope"
30
+ Issues = "https://github.com/AdeelMalik22/regscope/issues"
31
+
32
+ [project.scripts]
33
+ regscope = "regscope.cli:main"
34
+
35
+ [project.entry-points.pytest11]
36
+ regscope = "regscope.pytest_plugin"
37
+
38
+ [project.optional-dependencies]
39
+ dev = ["build", "pytest"]
40
+ db = ["SQLAlchemy>=1.4"]
41
+ http = ["requests>=2.28"]
42
+ redis = ["redis>=4.0"]
43
+ integration = ["SQLAlchemy>=1.4", "requests>=2.28", "redis>=4.0"]
44
+
45
+ [tool.setuptools.packages.find]
46
+ include = ["regscope*"]
47
+
48
+ [tool.setuptools.package-data]
49
+ regscope = ["py.typed"]
50
+
51
+ [tool.pytest.ini_options]
52
+ addopts = ["-ra"]
53
+ testpaths = ["tests"]
@@ -0,0 +1,6 @@
1
+ """RegScope: observed behavioral profiles for Python functions."""
2
+
3
+ from .api import TrackConfig, track
4
+
5
+ __all__ = ["TrackConfig", "__version__", "track"]
6
+ __version__ = "0.1.0.dev1"
@@ -0,0 +1,5 @@
1
+ from .cli import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ raise SystemExit(main())
@@ -0,0 +1,6 @@
1
+ """Public RegScope APIs."""
2
+
3
+ from .decorators import track
4
+ from .config import TrackConfig
5
+
6
+ __all__ = ["TrackConfig", "track"]
@@ -0,0 +1,50 @@
1
+ """Optional collector lifecycle for tracked executions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from contextlib import ExitStack, contextmanager
6
+ from typing import Any, Dict, Iterator
7
+
8
+ from ..collectors import HTTPCollector, MemoryCollector, RedisCollector, SQLAlchemyCollector
9
+ from .config import TrackConfig
10
+
11
+
12
+ @contextmanager
13
+ def active_collectors(config: TrackConfig, metrics: Dict[str, int]) -> Iterator[None]:
14
+ """Attach configured collectors and publish their metrics on exit."""
15
+ with ExitStack() as stack:
16
+ if config.sqlalchemy_engine is not None:
17
+ collector = SQLAlchemyCollector()
18
+ collector.attach(config.sqlalchemy_engine)
19
+ stack.callback(collector.detach)
20
+ metrics["db_queries"] = 0
21
+ stack.callback(lambda: metrics.update(db_queries=collector.query_count))
22
+
23
+ if config.collect_http:
24
+ collector = HTTPCollector()
25
+ collector.attach()
26
+ stack.callback(collector.detach)
27
+ metrics["http_requests"] = 0
28
+ stack.callback(lambda: metrics.update(http_requests=collector.request_count))
29
+
30
+ if config.collect_redis:
31
+ collector = RedisCollector()
32
+ collector.attach()
33
+ stack.callback(collector.detach)
34
+ metrics["redis_commands"] = 0
35
+ stack.callback(lambda: metrics.update(redis_commands=collector.command_count))
36
+
37
+ memory = None
38
+ if config.collect_memory:
39
+ memory = MemoryCollector()
40
+ stack.callback(
41
+ lambda: metrics.update(
42
+ memory_current_delta=memory.last_profile.current_delta,
43
+ memory_peak=memory.last_profile.peak,
44
+ )
45
+ if memory.last_profile is not None
46
+ else None
47
+ )
48
+ stack.enter_context(memory.measure())
49
+
50
+ yield
@@ -0,0 +1,27 @@
1
+ """Validated public configuration for tracked functions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from os import PathLike
7
+ from typing import Any, Union
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class TrackConfig:
12
+ """Configuration shared by a tracked function's collectors and storage."""
13
+
14
+ baseline_dir: Union[PathLike[str], str] = ".regscope"
15
+ history_dir: Union[PathLike[str], str] = ".regscope"
16
+ max_runs: int = 5
17
+ warmup_runs: int = 0
18
+ sqlalchemy_engine: Any = None
19
+ collect_http: bool = False
20
+ collect_redis: bool = False
21
+ collect_memory: bool = False
22
+
23
+ def __post_init__(self) -> None:
24
+ if self.max_runs < 1:
25
+ raise ValueError("max_runs must be at least 1")
26
+ if self.warmup_runs < 0:
27
+ raise ValueError("warmup_runs must not be negative")