hyperprobe-agent 1.2.30b6__tar.gz → 1.2.31b1__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 (56) hide show
  1. hyperprobe_agent-1.2.30b6/README.md → hyperprobe_agent-1.2.31b1/PKG-INFO +44 -67
  2. hyperprobe_agent-1.2.30b6/PKG-INFO → hyperprobe_agent-1.2.31b1/README.md +9 -112
  3. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/agent.py +27 -143
  4. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/broker.py +6 -4
  5. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/evaluator.py +11 -17
  6. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/logger.py +3 -15
  7. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/monitoring_engine.py +294 -68
  8. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/serializer.py +12 -6
  9. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/protos/agent_pb2.py +3 -2
  10. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe_agent.egg-info/PKG-INFO +13 -81
  11. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe_agent.egg-info/SOURCES.txt +0 -19
  12. hyperprobe_agent-1.2.31b1/hyperprobe_agent.egg-info/requires.txt +8 -0
  13. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/setup.py +4 -14
  14. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_fork.py +5 -5
  15. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_logger.py +0 -21
  16. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_metric_probes.py +2 -8
  17. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_multithreading_integration.py +2 -6
  18. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_probe_output.py +0 -28
  19. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_serializer.py +0 -26
  20. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_startup.py +0 -18
  21. hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/__init__.py +0 -1
  22. hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/controller.py +0 -300
  23. hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/discovery.py +0 -71
  24. hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/engine.py +0 -674
  25. hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/imports.py +0 -60
  26. hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/metric_processor.py +0 -262
  27. hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/records.py +0 -51
  28. hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/rewriting.py +0 -102
  29. hyperprobe_agent-1.2.30b6/hyperprobe/core/engine_base.py +0 -165
  30. hyperprobe_agent-1.2.30b6/hyperprobe/core/locations.py +0 -16
  31. hyperprobe_agent-1.2.30b6/hyperprobe/core/probe_processor.py +0 -163
  32. hyperprobe_agent-1.2.30b6/hyperprobe/core/redaction.py +0 -117
  33. hyperprobe_agent-1.2.30b6/hyperprobe_agent.egg-info/requires.txt +0 -19
  34. hyperprobe_agent-1.2.30b6/tests/test_agent_backend_integration.py +0 -229
  35. hyperprobe_agent-1.2.30b6/tests/test_bytecode_backend.py +0 -397
  36. hyperprobe_agent-1.2.30b6/tests/test_bytecode_lifecycle.py +0 -400
  37. hyperprobe_agent-1.2.30b6/tests/test_bytecode_token_lifecycle.py +0 -312
  38. hyperprobe_agent-1.2.30b6/tests/test_engine_selection.py +0 -37
  39. hyperprobe_agent-1.2.30b6/tests/test_redaction.py +0 -78
  40. hyperprobe_agent-1.2.30b6/tests/test_shared_processor.py +0 -220
  41. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/LICENSE +0 -0
  42. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/__init__.py +0 -0
  43. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/__init__.py +0 -0
  44. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/probe_output.py +0 -0
  45. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/quota.py +0 -0
  46. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/safety.py +0 -0
  47. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/trace_extractor.py +0 -0
  48. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/protos/__init__.py +0 -0
  49. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/protos/agent_pb2_grpc.py +0 -0
  50. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe_agent.egg-info/dependency_links.txt +0 -0
  51. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe_agent.egg-info/top_level.txt +0 -0
  52. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/setup.cfg +0 -0
  53. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_agent.py +0 -0
  54. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_evaluator.py +0 -0
  55. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_p1_regressions.py +0 -0
  56. {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_safety.py +0 -0
@@ -1,65 +1,53 @@
1
+ Metadata-Version: 2.4
2
+ Name: hyperprobe-agent
3
+ Version: 1.2.31b1
4
+ Summary: Production-grade, non-breaking live debugger and telemetry agent for Python.
5
+ Home-page: https://www.hyperprobe.co
6
+ Author: Arif, Saksham, Karan
7
+ Author-email: arif@hypertest.co, saksham@hypertest.co, karan@hypertest.co
8
+ License: Hyperprobe Proprietary License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.12
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: protobuf<6.0.0,>=4.25.3
17
+ Requires-Dist: grpcio>=1.62.2
18
+ Provides-Extra: dev
19
+ Requires-Dist: grpcio>=1.50.0; extra == "dev"
20
+ Requires-Dist: grpcio-tools>=1.50.0; extra == "dev"
21
+ Requires-Dist: protobuf>=4.21.0; extra == "dev"
22
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
23
+ Dynamic: author
24
+ Dynamic: author-email
25
+ Dynamic: classifier
26
+ Dynamic: description
27
+ Dynamic: description-content-type
28
+ Dynamic: home-page
29
+ Dynamic: license
30
+ Dynamic: license-file
31
+ Dynamic: provides-extra
32
+ Dynamic: requires-dist
33
+ Dynamic: requires-python
34
+ Dynamic: summary
35
+
1
36
  # HyperProbe Python Agent
2
37
 
3
38
  Production-grade, non-breaking live debugger and telemetry agent for Python.
4
39
 
5
- HyperProbe captures stack frames, local variables, and logs at selected source
6
- lines without stopping the application in a debugger. At agent initialization,
7
- Python 3.12+ selects the `sys.monitoring` backend. CPython 3.7–3.11 selects the
8
- bytecode backend. Both support snapshot, log, counter, metric, and duration
9
- probes and use the same broker, evaluator, serializer, quotas, safety monitor,
10
- and output pipeline.
11
- Only the selected backend is imported; `bytecode` is not a dependency on 3.12+.
40
+ HyperProbe allows you to debug running Python applications in real-time without breaking them, restarting them, or altering production traffic. It leverages modern Python's fast, low-overhead monitoring APIs (`sys.monitoring`) to securely and dynamically extract stack frames, local variables, and logs at targeted lines.
12
41
 
13
42
  ## Features
14
43
 
15
44
  - **Non-Breaking Snapshot Probes:** Capture local variables and stack traces dynamically at any line of code.
16
45
  - **Dynamic Log Probes:** Inject live log templates to print formatted output without redeploying code.
17
46
  - **Counter Probes:** Emit a value of `1` whenever a configured source line is reached.
18
- - **Metric Probes:** Evaluate a numerical Python expression and emit its value.
47
+ - **Metric Probes:** Safely evaluate a numerical Python expression and emit its value.
19
48
  - **Duration Probes:** Measure elapsed monotonic time in milliseconds between two source lines, with optional request correlation.
20
49
  - **Circular Reference & Deep Object Protection:** Memory-safe serialization of deeply nested or cyclic variables.
21
- - **Safety Guard:** Suspends instrumentation when scheduler lag or the capture pause budget crosses its configured threshold.
22
-
23
- ## Bytecode backend (CPython 3.7–3.11)
24
-
25
- The broker still supplies `runtime_location` and `runtime_line`. The backend
26
- uses the existing path resolver, locates functions through their loaded modules,
27
- and replaces only the selected functions' code objects. It does not use
28
- `sys.settrace`, `threading.settrace`, or a heap-wide `gc.get_objects()` scan.
29
- Pending probes can watch standard file imports; the normal loader executes the
30
- module after the SDK prepares its code. Loaded factories can register and
31
- instrument nested functions they create later. Conditions, watches, redaction,
32
- trace IDs, hit limits, and telemetry use the shared services. Duration probes
33
- index both source locations, including an end line in a later-imported module.
34
-
35
- This backend also selects its own agent lifecycle controller at startup; the
36
- Python 3.12+ lifecycle and `sys.monitoring` implementation remain separate.
37
- Broker responses are compared against stable local definition records. An
38
- unchanged response (including reordered probes) does no module discovery,
39
- function planning, or bytecode rewriting. Condition, watch, and template changes
40
- replace evaluation state without rebuilding bytecode. Location changes discover
41
- the new target and replan affected functions; removal uses tracked functions.
42
- Callback-site tokens are held weakly, so short-lived nested functions do not
43
- accumulate permanent lookup entries.
44
- Broker-confirmed completed probes stay disabled until the broker removes them.
45
-
46
- On RED, the backend closes its capture gate and synchronously restores the
47
- saved original code for functions it still owns, including factories. Pending
48
- import work cannot install new probes while suspended. Restoration runs before
49
- agent bookkeeping or logging, and recovery builds bytecode without holding the
50
- agent lock. Capture delivery checks the original definition identity and
51
- activation generation before queue admission, so an old capture cannot be
52
- accepted under an updated definition or after RED/recovery. A frame already running
53
- keeps its existing code, but its callbacks become no-ops. An expression already
54
- running cannot be forcibly interrupted safely. Instrumentation failures are
55
- reported through bounded engine diagnostics and best-effort error logging;
56
- normal exceptions from the application's own function or module still propagate.
57
-
58
- Discovery supports module/class namespaces and bounded built-in containers.
59
- Arbitrary generated code, custom loaders, hidden pre-existing closures, and
60
- automatic reload coverage are not guaranteed. Function prologues are not probe
61
- locations. Performance, including a 2% overhead target, requires workload-based
62
- measurement; the shared evaluator is not a security sandbox.
50
+ - **High-Performance Safety Guard:** Automatically pauses monitoring and goes idle if CPU/memory boundaries or pause budgets are exceeded.
63
51
 
64
52
  ## Metric-family probes
65
53
 
@@ -71,9 +59,9 @@ Counter, metric, and duration probes are created through HyperProbe in the same
71
59
  | Metric | `metric_name`, `metric_expression`, and one runtime location | The finite numerical expression result |
72
60
  | Duration | `metric_name`, primary and secondary runtime locations, and optional `correlation_expression` | Monotonic elapsed milliseconds |
73
61
 
74
- Conditions are evaluated at every configured location. Metric expressions use the same restricted Python evaluator as conditions and watches. Numerical strings follow Node SDK `parseFloat` prefix behavior for compatibility; invalid or non-finite values are exported with `metric_value` set to `0` and a populated `capture_error`. The bytecode backend examines at most the first 1,024 characters of a metric string.
62
+ Conditions are evaluated at every configured location. Metric expressions use the same restricted, read-only Python evaluator as conditions and watches. Numerical strings follow Node SDK `parseFloat` behavior for compatibility; invalid or non-finite values are exported with `metric_value` set to `0` and a populated `capture_error`.
75
63
 
76
- Duration correlation expressions must evaluate to a string or number. When omitted or when they evaluate to `None`, a singleton correlation key is used. Unmatched duration ends are ignored, and pending starts expire after 60 seconds. On CPython 3.7–3.11, correlation keys are limited to 256 characters and at most 4,096 starts are kept per process; the oldest start is evicted if that limit is reached. Duration starts never consume a probe hit. A matching end consumes the hit when its event is admitted to the telemetry queue.
64
+ Duration correlation expressions must evaluate to a string or number. When omitted or when they evaluate to `None`, a singleton correlation key is used. Unmatched duration ends are ignored, and pending starts expire after 60 seconds.
77
65
 
78
66
  ## Installation
79
67
 
@@ -118,7 +106,7 @@ Configure the agent using the following environment variables:
118
106
  | `HYPERPROBE_SYNC_INTERVAL_MS` | Rate at which the agent syncs probe definitions from the broker. | `60000` (1 min) |
119
107
  | `HYPERPROBE_FLUSH_INTERVAL_MS` | Delay before sending telemetry events to the broker. | `1000` (1 sec) |
120
108
  | `HYPERPROBE_MAX_QUEUE_SIZE` | Maximum number of telemetry events held before flush. | `100` |
121
- | `HYPERPROBE_HITS_PER_SEC` | Max quota-charged probe evaluations per second. | `10` |
109
+ | `HYPERPROBE_HITS_PER_SEC` | Max snapshot capture requests per second (quota). | `10` |
122
110
  | `HYPERPROBE_BANDWIDTH_KB_PER_SEC` | Max bandwidth limit for telemetry transmissions (quota). | `1024` (1 MB) |
123
111
  | `HYPERPROBE_RPC_TIMEOUT_SEC` | Deadline for broker RPCs. | `10` |
124
112
  | `HYPERPROBE_COOLDOWN_SEC` | Tracing suspension duration after a RED safety state. | `10` |
@@ -140,20 +128,9 @@ startup while the host application continues normally.
140
128
 
141
129
  ### Safe Evaluation & Security Considerations
142
130
 
143
- Captured strings are checked against configured value-redaction patterns before
144
- truncation. When value rules are configured, a string longer than 16,384
145
- characters is masked rather than scanned partially. Log substitutions use the
146
- bounded serializer, so nested sensitive keys are masked before an object becomes
147
- log text. Telemetry errors carry categories rather than raw runtime values or
148
- exception messages. These rules do not infer the origin of an arbitrary watch
149
- result: a scalar watch expression such as `password` can still expose its value
150
- when no value pattern matches it.
151
-
152
- By default, **safe evaluation is enabled** (`HYPERPROBE_DISABLE_SAFE_EVALUATION=false`).
153
- The existing evaluator checks function and method names against an allowlist.
154
- This reduces available operations but is not a sandbox: properties, overloaded
155
- operators, or allowed method names on custom objects can still execute
156
- application code. There is no hard timeout that can interrupt such code safely.
131
+ By default, **safe evaluation is enabled** (`HYPERPROBE_DISABLE_SAFE_EVALUATION=false`). In safe mode:
132
+ - Method and function calls are strictly restricted to a narrow allowlist of side-effect free standard operations.
133
+ - State mutations, assignments, and arbitrary function invocations (such as database calls, network I/O, file operations, or state modifications) are prevented.
157
134
 
158
135
  **Disabling Safe Evaluation (`HYPERPROBE_DISABLE_SAFE_EVALUATION=true`):**
159
136
  > ⚠️ **Security Warning**: Setting `HYPERPROBE_DISABLE_SAFE_EVALUATION=true` allows conditions, watch expressions, and log templates to execute arbitrary functions and methods in the host application process. This may lead to unintended side effects, state mutations, performance degradation, or security risks if expressions invoke mutating or blocking operations. Only disable safe evaluation in trusted environments where dynamic method evaluation is strictly required.
@@ -1,110 +1,18 @@
1
- Metadata-Version: 2.4
2
- Name: hyperprobe-agent
3
- Version: 1.2.30b6
4
- Summary: Production-grade, non-breaking live debugger and telemetry agent for Python.
5
- Home-page: https://www.hyperprobe.co
6
- Author: Arif, Saksham, Karan
7
- Author-email: arif@hypertest.co, saksham@hypertest.co, karan@hypertest.co
8
- License: Hyperprobe Proprietary License
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.7
11
- Classifier: Programming Language :: Python :: 3.8
12
- Classifier: Programming Language :: Python :: 3.9
13
- Classifier: Programming Language :: Python :: 3.10
14
- Classifier: Programming Language :: Python :: 3.11
15
- Classifier: Programming Language :: Python :: 3.12
16
- Classifier: Programming Language :: Python :: 3.13
17
- Classifier: Operating System :: OS Independent
18
- Requires-Python: >=3.7
19
- Description-Content-Type: text/markdown
20
- License-File: LICENSE
21
- Requires-Dist: protobuf<4.25,>=4.24.4; python_version < "3.8"
22
- Requires-Dist: protobuf<6.0.0,>=4.25.3; python_version >= "3.8"
23
- Requires-Dist: grpcio<1.63,>=1.62.2; python_version < "3.8"
24
- Requires-Dist: grpcio>=1.62.2; python_version >= "3.8"
25
- Requires-Dist: importlib-metadata<7,>=6; python_version < "3.8"
26
- Requires-Dist: bytecode==0.13.0; python_version < "3.8"
27
- Requires-Dist: bytecode==0.16.2; python_version >= "3.8" and python_version < "3.12"
28
- Provides-Extra: dev
29
- Requires-Dist: grpcio>=1.50.0; extra == "dev"
30
- Requires-Dist: grpcio-tools>=1.50.0; extra == "dev"
31
- Requires-Dist: protobuf>=4.21.0; extra == "dev"
32
- Requires-Dist: pytest>=7.0.0; extra == "dev"
33
- Dynamic: author
34
- Dynamic: author-email
35
- Dynamic: classifier
36
- Dynamic: description
37
- Dynamic: description-content-type
38
- Dynamic: home-page
39
- Dynamic: license
40
- Dynamic: license-file
41
- Dynamic: provides-extra
42
- Dynamic: requires-dist
43
- Dynamic: requires-python
44
- Dynamic: summary
45
-
46
1
  # HyperProbe Python Agent
47
2
 
48
3
  Production-grade, non-breaking live debugger and telemetry agent for Python.
49
4
 
50
- HyperProbe captures stack frames, local variables, and logs at selected source
51
- lines without stopping the application in a debugger. At agent initialization,
52
- Python 3.12+ selects the `sys.monitoring` backend. CPython 3.7–3.11 selects the
53
- bytecode backend. Both support snapshot, log, counter, metric, and duration
54
- probes and use the same broker, evaluator, serializer, quotas, safety monitor,
55
- and output pipeline.
56
- Only the selected backend is imported; `bytecode` is not a dependency on 3.12+.
5
+ HyperProbe allows you to debug running Python applications in real-time without breaking them, restarting them, or altering production traffic. It leverages modern Python's fast, low-overhead monitoring APIs (`sys.monitoring`) to securely and dynamically extract stack frames, local variables, and logs at targeted lines.
57
6
 
58
7
  ## Features
59
8
 
60
9
  - **Non-Breaking Snapshot Probes:** Capture local variables and stack traces dynamically at any line of code.
61
10
  - **Dynamic Log Probes:** Inject live log templates to print formatted output without redeploying code.
62
11
  - **Counter Probes:** Emit a value of `1` whenever a configured source line is reached.
63
- - **Metric Probes:** Evaluate a numerical Python expression and emit its value.
12
+ - **Metric Probes:** Safely evaluate a numerical Python expression and emit its value.
64
13
  - **Duration Probes:** Measure elapsed monotonic time in milliseconds between two source lines, with optional request correlation.
65
14
  - **Circular Reference & Deep Object Protection:** Memory-safe serialization of deeply nested or cyclic variables.
66
- - **Safety Guard:** Suspends instrumentation when scheduler lag or the capture pause budget crosses its configured threshold.
67
-
68
- ## Bytecode backend (CPython 3.7–3.11)
69
-
70
- The broker still supplies `runtime_location` and `runtime_line`. The backend
71
- uses the existing path resolver, locates functions through their loaded modules,
72
- and replaces only the selected functions' code objects. It does not use
73
- `sys.settrace`, `threading.settrace`, or a heap-wide `gc.get_objects()` scan.
74
- Pending probes can watch standard file imports; the normal loader executes the
75
- module after the SDK prepares its code. Loaded factories can register and
76
- instrument nested functions they create later. Conditions, watches, redaction,
77
- trace IDs, hit limits, and telemetry use the shared services. Duration probes
78
- index both source locations, including an end line in a later-imported module.
79
-
80
- This backend also selects its own agent lifecycle controller at startup; the
81
- Python 3.12+ lifecycle and `sys.monitoring` implementation remain separate.
82
- Broker responses are compared against stable local definition records. An
83
- unchanged response (including reordered probes) does no module discovery,
84
- function planning, or bytecode rewriting. Condition, watch, and template changes
85
- replace evaluation state without rebuilding bytecode. Location changes discover
86
- the new target and replan affected functions; removal uses tracked functions.
87
- Callback-site tokens are held weakly, so short-lived nested functions do not
88
- accumulate permanent lookup entries.
89
- Broker-confirmed completed probes stay disabled until the broker removes them.
90
-
91
- On RED, the backend closes its capture gate and synchronously restores the
92
- saved original code for functions it still owns, including factories. Pending
93
- import work cannot install new probes while suspended. Restoration runs before
94
- agent bookkeeping or logging, and recovery builds bytecode without holding the
95
- agent lock. Capture delivery checks the original definition identity and
96
- activation generation before queue admission, so an old capture cannot be
97
- accepted under an updated definition or after RED/recovery. A frame already running
98
- keeps its existing code, but its callbacks become no-ops. An expression already
99
- running cannot be forcibly interrupted safely. Instrumentation failures are
100
- reported through bounded engine diagnostics and best-effort error logging;
101
- normal exceptions from the application's own function or module still propagate.
102
-
103
- Discovery supports module/class namespaces and bounded built-in containers.
104
- Arbitrary generated code, custom loaders, hidden pre-existing closures, and
105
- automatic reload coverage are not guaranteed. Function prologues are not probe
106
- locations. Performance, including a 2% overhead target, requires workload-based
107
- measurement; the shared evaluator is not a security sandbox.
15
+ - **High-Performance Safety Guard:** Automatically pauses monitoring and goes idle if CPU/memory boundaries or pause budgets are exceeded.
108
16
 
109
17
  ## Metric-family probes
110
18
 
@@ -116,9 +24,9 @@ Counter, metric, and duration probes are created through HyperProbe in the same
116
24
  | Metric | `metric_name`, `metric_expression`, and one runtime location | The finite numerical expression result |
117
25
  | Duration | `metric_name`, primary and secondary runtime locations, and optional `correlation_expression` | Monotonic elapsed milliseconds |
118
26
 
119
- Conditions are evaluated at every configured location. Metric expressions use the same restricted Python evaluator as conditions and watches. Numerical strings follow Node SDK `parseFloat` prefix behavior for compatibility; invalid or non-finite values are exported with `metric_value` set to `0` and a populated `capture_error`. The bytecode backend examines at most the first 1,024 characters of a metric string.
27
+ Conditions are evaluated at every configured location. Metric expressions use the same restricted, read-only Python evaluator as conditions and watches. Numerical strings follow Node SDK `parseFloat` behavior for compatibility; invalid or non-finite values are exported with `metric_value` set to `0` and a populated `capture_error`.
120
28
 
121
- Duration correlation expressions must evaluate to a string or number. When omitted or when they evaluate to `None`, a singleton correlation key is used. Unmatched duration ends are ignored, and pending starts expire after 60 seconds. On CPython 3.7–3.11, correlation keys are limited to 256 characters and at most 4,096 starts are kept per process; the oldest start is evicted if that limit is reached. Duration starts never consume a probe hit. A matching end consumes the hit when its event is admitted to the telemetry queue.
29
+ Duration correlation expressions must evaluate to a string or number. When omitted or when they evaluate to `None`, a singleton correlation key is used. Unmatched duration ends are ignored, and pending starts expire after 60 seconds.
122
30
 
123
31
  ## Installation
124
32
 
@@ -163,7 +71,7 @@ Configure the agent using the following environment variables:
163
71
  | `HYPERPROBE_SYNC_INTERVAL_MS` | Rate at which the agent syncs probe definitions from the broker. | `60000` (1 min) |
164
72
  | `HYPERPROBE_FLUSH_INTERVAL_MS` | Delay before sending telemetry events to the broker. | `1000` (1 sec) |
165
73
  | `HYPERPROBE_MAX_QUEUE_SIZE` | Maximum number of telemetry events held before flush. | `100` |
166
- | `HYPERPROBE_HITS_PER_SEC` | Max quota-charged probe evaluations per second. | `10` |
74
+ | `HYPERPROBE_HITS_PER_SEC` | Max snapshot capture requests per second (quota). | `10` |
167
75
  | `HYPERPROBE_BANDWIDTH_KB_PER_SEC` | Max bandwidth limit for telemetry transmissions (quota). | `1024` (1 MB) |
168
76
  | `HYPERPROBE_RPC_TIMEOUT_SEC` | Deadline for broker RPCs. | `10` |
169
77
  | `HYPERPROBE_COOLDOWN_SEC` | Tracing suspension duration after a RED safety state. | `10` |
@@ -185,20 +93,9 @@ startup while the host application continues normally.
185
93
 
186
94
  ### Safe Evaluation & Security Considerations
187
95
 
188
- Captured strings are checked against configured value-redaction patterns before
189
- truncation. When value rules are configured, a string longer than 16,384
190
- characters is masked rather than scanned partially. Log substitutions use the
191
- bounded serializer, so nested sensitive keys are masked before an object becomes
192
- log text. Telemetry errors carry categories rather than raw runtime values or
193
- exception messages. These rules do not infer the origin of an arbitrary watch
194
- result: a scalar watch expression such as `password` can still expose its value
195
- when no value pattern matches it.
196
-
197
- By default, **safe evaluation is enabled** (`HYPERPROBE_DISABLE_SAFE_EVALUATION=false`).
198
- The existing evaluator checks function and method names against an allowlist.
199
- This reduces available operations but is not a sandbox: properties, overloaded
200
- operators, or allowed method names on custom objects can still execute
201
- application code. There is no hard timeout that can interrupt such code safely.
96
+ By default, **safe evaluation is enabled** (`HYPERPROBE_DISABLE_SAFE_EVALUATION=false`). In safe mode:
97
+ - Method and function calls are strictly restricted to a narrow allowlist of side-effect free standard operations.
98
+ - State mutations, assignments, and arbitrary function invocations (such as database calls, network I/O, file operations, or state modifications) are prevented.
202
99
 
203
100
  **Disabling Safe Evaluation (`HYPERPROBE_DISABLE_SAFE_EVALUATION=true`):**
204
101
  > ⚠️ **Security Warning**: Setting `HYPERPROBE_DISABLE_SAFE_EVALUATION=true` allows conditions, watch expressions, and log templates to execute arbitrary functions and methods in the host application process. This may lead to unintended side effects, state mutations, performance degradation, or security risks if expressions invoke mutating or blocking operations. Only disable safe evaluation in trusted environments where dynamic method evaluation is strictly required.
@@ -5,7 +5,6 @@ import time
5
5
  import threading
6
6
  import queue
7
7
  import re
8
- import math
9
8
  from urllib.parse import urlparse
10
9
 
11
10
  from hyperprobe.core.quota import QuotaManager
@@ -29,48 +28,6 @@ class _TelemetryItem:
29
28
  self.bandwidth_reservation = bandwidth_reservation
30
29
 
31
30
 
32
- def _numeric_option(
33
- options, option_name, env_name, default, *, integer=False,
34
- allow_zero=False, scale=1.0, max_value=None,
35
- ):
36
- """Validate an option or environment value before allocating SDK resources."""
37
- value = options.get(option_name)
38
- if value is None:
39
- value = os.getenv(env_name, default)
40
-
41
- requirement = "a positive integer" if integer else (
42
- "a non-negative finite number" if allow_zero else "a positive finite number"
43
- )
44
- if max_value is not None:
45
- requirement += " within the platform's timeout range"
46
-
47
- try:
48
- if isinstance(value, bool):
49
- raise ValueError("Boolean configuration is not numeric")
50
- if integer:
51
- if not isinstance(value, (int, str)):
52
- raise ValueError("Integer configuration must not be fractional")
53
- parsed = int(value)
54
- else:
55
- parsed = float(value)
56
- if not math.isfinite(parsed):
57
- raise ValueError("Configuration must be finite")
58
- parsed *= scale
59
- if not math.isfinite(parsed):
60
- raise ValueError("Converted configuration must be finite")
61
- if parsed < 0 or (not allow_zero and parsed == 0):
62
- raise ValueError("Configuration is outside its allowed range")
63
- if max_value is not None and parsed > max_value:
64
- raise ValueError("Configuration exceeds the platform's timeout range")
65
- return parsed
66
- except (TypeError, ValueError, OverflowError):
67
- message = "[HyperProbe] Invalid configuration: %s (%s) must be %s." % (
68
- option_name, env_name, requirement,
69
- )
70
- logger.forceError(message)
71
- raise ValueError("Invalid numeric configuration: " + option_name) from None
72
-
73
-
74
31
  def is_valid_broker_url(url):
75
32
  if not url or not str(url).strip():
76
33
  return False
@@ -109,39 +66,17 @@ class HyperProbeAgent:
109
66
  if self.fork_mode not in {"none", "worker"}:
110
67
  raise ValueError('fork_mode must be either "none" or "worker"')
111
68
 
112
- # Validate operational settings before creating process resources.
113
- self.sync_interval_sec = _numeric_option(
114
- options, "sync_interval_ms", "HYPERPROBE_SYNC_INTERVAL_MS", 60000,
115
- scale=0.001, max_value=threading.TIMEOUT_MAX,
116
- )
117
- self.flush_interval_sec = _numeric_option(
118
- options, "flush_interval_ms", "HYPERPROBE_FLUSH_INTERVAL_MS", 1000,
119
- scale=0.001, max_value=threading.TIMEOUT_MAX,
120
- )
121
- self.max_queue_size = _numeric_option(
122
- options, "max_queue_size", "HYPERPROBE_MAX_QUEUE_SIZE", 100,
123
- integer=True,
124
- )
125
- self._cooldown_sec = _numeric_option(
126
- options, "hyperprobe_cooldown_sec", "HYPERPROBE_COOLDOWN_SEC", 10,
127
- allow_zero=True, max_value=threading.TIMEOUT_MAX,
128
- )
129
- hits_per_sec = _numeric_option(
130
- options, "hits_per_sec", "HYPERPROBE_HITS_PER_SEC", 10,
131
- )
132
- bandwidth_bytes = _numeric_option(
133
- options, "bandwidth_kb_per_sec", "HYPERPROBE_BANDWIDTH_KB_PER_SEC", 1024,
134
- scale=1024.0,
135
- )
136
- rpc_timeout_sec = _numeric_option(
137
- options, "hyperprobe_rpc_timeout_sec", "HYPERPROBE_RPC_TIMEOUT_SEC", 10.0,
138
- )
139
- max_lag_ms = _numeric_option(
140
- options, "hyperprobe_max_lag_ms", "HYPERPROBE_MAX_LAG_MS", 50.0,
141
- )
142
- pause_budget_ms = _numeric_option(
143
- options, "hyperprobe_pause_budget_ms", "HYPERPROBE_PAUSE_BUDGET_MS", 15.0,
144
- )
69
+ # Options & Envs Parsing
70
+ self.sync_interval_sec = (options.get("sync_interval_ms") or int(os.getenv("HYPERPROBE_SYNC_INTERVAL_MS", 60000))) / 1000.0
71
+ self.flush_interval_sec = (options.get("flush_interval_ms") or int(os.getenv("HYPERPROBE_FLUSH_INTERVAL_MS", 1000))) / 1000.0
72
+ self.max_queue_size = options.get("max_queue_size") or int(os.getenv("HYPERPROBE_MAX_QUEUE_SIZE", 100))
73
+ self._cooldown_sec = options.get("hyperprobe_cooldown_sec") or int(os.getenv("HYPERPROBE_COOLDOWN_SEC", 10))
74
+
75
+ hits_per_sec = options.get("hits_per_sec") or int(os.getenv("HYPERPROBE_HITS_PER_SEC", 10))
76
+ bandwidth_kb = options.get("bandwidth_kb_per_sec") or int(os.getenv("HYPERPROBE_BANDWIDTH_KB_PER_SEC", 1024))
77
+ rpc_timeout_sec = options.get("hyperprobe_rpc_timeout_sec") or float(os.getenv("HYPERPROBE_RPC_TIMEOUT_SEC", 10.0))
78
+ max_lag_ms = options.get("hyperprobe_max_lag_ms") or float(os.getenv("HYPERPROBE_MAX_LAG_MS", 50.0))
79
+ pause_budget_ms = options.get("hyperprobe_pause_budget_ms") or float(os.getenv("HYPERPROBE_PAUSE_BUDGET_MS", 15.0))
145
80
 
146
81
  opt_disable_safe_eval = options.get("disable_safe_evaluation", False)
147
82
  disable_safe_eval = (
@@ -175,7 +110,7 @@ class HyperProbeAgent:
175
110
  self.global_config["redact_values"] = [v.strip() for v in self.global_config["redact_values"] if v.strip()]
176
111
 
177
112
  # Initialize core telemetry components
178
- self.quota_manager = QuotaManager(hits_per_sec, bandwidth_bytes)
113
+ self.quota_manager = QuotaManager(hits_per_sec, bandwidth_kb * 1024)
179
114
  self.safety_monitor = SafetyMonitor(
180
115
  self._handle_health_change,
181
116
  max_lag_ms=max_lag_ms,
@@ -205,9 +140,7 @@ class HyperProbeAgent:
205
140
 
206
141
  # Determine and initialize dynamic instrumentation engine
207
142
  self.engine = self._init_instrumentation_engine()
208
- self.supported_probe_types = self.engine.supported_probe_types
209
- if not self.engine.set_global_config(self.global_config):
210
- raise ValueError("Initial capture configuration could not be applied")
143
+ self.engine.set_global_config(self.global_config)
211
144
 
212
145
  # Background worker threads
213
146
  self.sync_thread = None
@@ -217,11 +150,7 @@ class HyperProbeAgent:
217
150
  @classmethod
218
151
  def start(cls, options):
219
152
  """Starts the global HyperProbe Agent singleton."""
220
- try:
221
- options = dict(options or {})
222
- except Exception:
223
- logger.forceError("[HyperProbe] Agent options must be a mapping. Running application uninstrumented.")
224
- return None
153
+ options = dict(options or {})
225
154
  with cls._lock:
226
155
  if (
227
156
  cls._fork_owner_pid == os.getpid()
@@ -312,7 +241,7 @@ class HyperProbeAgent:
312
241
  cls._instance = None
313
242
  logger.forceError(
314
243
  f"[HyperProbe] Agent failed to initialize "
315
- f"({type(exc).__name__}). "
244
+ f"({type(exc).__name__}: {exc}). "
316
245
  "Running application uninstrumented."
317
246
  )
318
247
  return None
@@ -406,7 +335,7 @@ class HyperProbeAgent:
406
335
  instance._cleanup_failed_start()
407
336
  cls._instance = None
408
337
  logger.forceError(
409
- f"[HyperProbe] Failed to restart the agent after fork ({type(exc).__name__})."
338
+ f"[HyperProbe] Failed to restart the agent after fork: {exc}"
410
339
  )
411
340
  return
412
341
 
@@ -431,7 +360,7 @@ class HyperProbeAgent:
431
360
  cls._instance = None
432
361
  if cls._fork_owner_pid == os.getpid():
433
362
  cls._clear_fork_state_locked()
434
- logger.forceInfo("[HyperProbe] Python agent successfully shut down.")
363
+ print("[HyperProbe] Python agent successfully shut down.")
435
364
 
436
365
  @classmethod
437
366
  def _clear_fork_state_locked(cls):
@@ -441,20 +370,8 @@ class HyperProbeAgent:
441
370
  cls._fork_pending = False
442
371
 
443
372
  def _init_instrumentation_engine(self):
444
- if sys.version_info >= (3, 12):
445
- from hyperprobe.core.monitoring_engine import MonitoringEngine
446
- engine_class = MonitoringEngine
447
- elif sys.version_info >= (3, 7):
448
- from hyperprobe.core.bytecode.controller import BytecodeController
449
- from hyperprobe.core.bytecode.engine import BytecodeEngine
450
- # Select lifecycle handlers once. Python 3.12+ keeps the existing
451
- # monitoring handlers, without runtime branches on captures/polls.
452
- self._bytecode_controller = BytecodeController(self)
453
- self._bytecode_controller.bind()
454
- engine_class = BytecodeEngine
455
- else:
456
- raise RuntimeError("HyperProbe requires Python 3.7 or newer")
457
- return engine_class(
373
+ from hyperprobe.core.monitoring_engine import MonitoringEngine
374
+ return MonitoringEngine(
458
375
  self.quota_manager,
459
376
  self.safety_monitor,
460
377
  self._handle_capture,
@@ -614,7 +531,7 @@ class HyperProbeAgent:
614
531
  # Only activate probe types implemented by this SDK.
615
532
  server_probes = {}
616
533
  for probe in response.probes:
617
- if probe.type in getattr(self, "supported_probe_types", SUPPORTED_PROBE_TYPES):
534
+ if probe.type in SUPPORTED_PROBE_TYPES:
618
535
  server_probes[probe.id] = probe
619
536
  else:
620
537
  msg = f"[HyperProbe] Ignoring unsupported probe type {probe.type} for probe {probe.id}."
@@ -665,8 +582,8 @@ class HyperProbeAgent:
665
582
  batch = self._inflight_batch
666
583
  if batch is None:
667
584
  batch = []
668
- # Concurrent producers can keep this drain going beyond queue capacity.
669
- # Keep the batch in flight until the broker confirms delivery.
585
+ # Pull all currently queued events up to capacity. Keep the batch
586
+ # in-flight until the broker call succeeds so retries cannot lose it.
670
587
  while not self.telemetry_queue.empty():
671
588
  try:
672
589
  item = self.telemetry_queue.get_nowait()
@@ -753,37 +670,18 @@ class HyperProbeAgent:
753
670
  return list(self.active_probes.values())
754
671
 
755
672
  def _handle_capture(self, event):
756
- try:
757
- return self._handle_capture_impl(event)
758
- except BaseException as error:
759
- # This callback runs inside an application frame via either backend.
760
- # Even a broken diagnostic stream must not let SDK errors escape.
761
- try:
762
- report = getattr(self.engine, "_record_failure", None)
763
- if report is not None:
764
- report("capture admission", error)
765
- return
766
- logger.forceError(
767
- "[HyperProbe] Capture admission failed (%s)." % type(error).__name__
768
- )
769
- except BaseException:
770
- pass
771
-
772
- def _handle_capture_impl(self, event):
773
673
  if (
774
674
  getattr(self, "owner_pid", os.getpid()) != os.getpid()
775
675
  or getattr(self, "is_shutdown", False)
776
- or getattr(self.engine, "is_suspended", False)
777
676
  ):
778
677
  return
779
- generation = getattr(self.engine, "_generation", None)
780
678
  probe_id = event["probe_id"]
781
679
  with self._agent_lock:
782
680
  probe = self.active_probes.get(probe_id)
783
681
  if not probe:
784
682
  return
785
683
 
786
- if probe.type not in getattr(self, "supported_probe_types", SUPPORTED_PROBE_TYPES):
684
+ if probe.type not in SUPPORTED_PROBE_TYPES:
787
685
  return
788
686
 
789
687
  if not self._reserve_hit_slot(probe_id, probe):
@@ -799,11 +697,7 @@ class HyperProbeAgent:
799
697
  self._release_hit_slot(probe_id)
800
698
  return
801
699
 
802
- try:
803
- bandwidth_reservation = self.quota_manager.reserve_bandwidth(event_size)
804
- except BaseException:
805
- self._release_hit_slot(probe_id)
806
- raise
700
+ bandwidth_reservation = self.quota_manager.reserve_bandwidth(event_size)
807
701
  if bandwidth_reservation is None:
808
702
  msg = f"[HyperProbe] Bandwidth quota exceeded; dropping event for probe {probe_id} , event size was : {event_size} bytes."
809
703
  logger.error(msg)
@@ -812,24 +706,14 @@ class HyperProbeAgent:
812
706
 
813
707
  # Hold the bandwidth reservation with the event until the broker
814
708
  # confirms the batch. Release it if local queue admission fails.
709
+ item = _TelemetryItem(event, bandwidth_reservation)
815
710
  try:
816
- item = _TelemetryItem(event, bandwidth_reservation)
817
- with self._agent_lock:
818
- if (
819
- getattr(self, "is_shutdown", False)
820
- or getattr(self.engine, "is_suspended", False)
821
- or generation != getattr(self.engine, "_generation", None)
822
- or self.active_probes.get(probe_id) is not probe
823
- ):
824
- bandwidth_reservation.release()
825
- self._release_hit_slot(probe_id)
826
- return
827
- self.telemetry_queue.put_nowait(item)
711
+ self.telemetry_queue.put_nowait(item)
828
712
  except queue.Full:
829
713
  bandwidth_reservation.release()
830
714
  self._release_hit_slot(probe_id)
831
715
  return
832
- except BaseException as exc:
716
+ except Exception as exc:
833
717
  bandwidth_reservation.release()
834
718
  self._release_hit_slot(probe_id)
835
719
  logger.error(
@@ -1,4 +1,6 @@
1
1
  import os
2
+ import sys
3
+ import time
2
4
 
3
5
  try:
4
6
  # Shaded/vendored fallback
@@ -9,6 +11,9 @@ except ImportError:
9
11
  # Import our generated protos
10
12
  from hyperprobe.protos import agent_pb2
11
13
  from hyperprobe.protos import agent_pb2_grpc
14
+ from hyperprobe.core.logger import get_logger
15
+
16
+ logger = get_logger("hyperprobe:broker")
12
17
 
13
18
 
14
19
  class BrokerTransportError(RuntimeError):
@@ -32,10 +37,7 @@ class BrokerClient:
32
37
  self.agent_id = agent_id
33
38
  if agent_version is None:
34
39
  try:
35
- try:
36
- from importlib.metadata import version
37
- except ImportError:
38
- from importlib_metadata import version
40
+ from importlib.metadata import version
39
41
  agent_version = version("hyperprobe-agent")
40
42
  except Exception:
41
43
  agent_version = "unknown"