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.
- hyperprobe_agent-1.2.30b6/README.md → hyperprobe_agent-1.2.31b1/PKG-INFO +44 -67
- hyperprobe_agent-1.2.30b6/PKG-INFO → hyperprobe_agent-1.2.31b1/README.md +9 -112
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/agent.py +27 -143
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/broker.py +6 -4
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/evaluator.py +11 -17
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/logger.py +3 -15
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/monitoring_engine.py +294 -68
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/serializer.py +12 -6
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/protos/agent_pb2.py +3 -2
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe_agent.egg-info/PKG-INFO +13 -81
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe_agent.egg-info/SOURCES.txt +0 -19
- hyperprobe_agent-1.2.31b1/hyperprobe_agent.egg-info/requires.txt +8 -0
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/setup.py +4 -14
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_fork.py +5 -5
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_logger.py +0 -21
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_metric_probes.py +2 -8
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_multithreading_integration.py +2 -6
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_probe_output.py +0 -28
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_serializer.py +0 -26
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_startup.py +0 -18
- hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/__init__.py +0 -1
- hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/controller.py +0 -300
- hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/discovery.py +0 -71
- hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/engine.py +0 -674
- hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/imports.py +0 -60
- hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/metric_processor.py +0 -262
- hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/records.py +0 -51
- hyperprobe_agent-1.2.30b6/hyperprobe/core/bytecode/rewriting.py +0 -102
- hyperprobe_agent-1.2.30b6/hyperprobe/core/engine_base.py +0 -165
- hyperprobe_agent-1.2.30b6/hyperprobe/core/locations.py +0 -16
- hyperprobe_agent-1.2.30b6/hyperprobe/core/probe_processor.py +0 -163
- hyperprobe_agent-1.2.30b6/hyperprobe/core/redaction.py +0 -117
- hyperprobe_agent-1.2.30b6/hyperprobe_agent.egg-info/requires.txt +0 -19
- hyperprobe_agent-1.2.30b6/tests/test_agent_backend_integration.py +0 -229
- hyperprobe_agent-1.2.30b6/tests/test_bytecode_backend.py +0 -397
- hyperprobe_agent-1.2.30b6/tests/test_bytecode_lifecycle.py +0 -400
- hyperprobe_agent-1.2.30b6/tests/test_bytecode_token_lifecycle.py +0 -312
- hyperprobe_agent-1.2.30b6/tests/test_engine_selection.py +0 -37
- hyperprobe_agent-1.2.30b6/tests/test_redaction.py +0 -78
- hyperprobe_agent-1.2.30b6/tests/test_shared_processor.py +0 -220
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/LICENSE +0 -0
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/__init__.py +0 -0
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/__init__.py +0 -0
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/probe_output.py +0 -0
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/quota.py +0 -0
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/safety.py +0 -0
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/core/trace_extractor.py +0 -0
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/protos/__init__.py +0 -0
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe/protos/agent_pb2_grpc.py +0 -0
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe_agent.egg-info/dependency_links.txt +0 -0
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/hyperprobe_agent.egg-info/top_level.txt +0 -0
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/setup.cfg +0 -0
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_agent.py +0 -0
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_evaluator.py +0 -0
- {hyperprobe_agent-1.2.30b6 → hyperprobe_agent-1.2.31b1}/tests/test_p1_regressions.py +0 -0
- {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
|
|
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:**
|
|
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:**
|
|
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`
|
|
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.
|
|
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
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
|
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:**
|
|
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:**
|
|
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`
|
|
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.
|
|
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
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
#
|
|
113
|
-
self.sync_interval_sec =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
445
|
-
|
|
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
|
|
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
|
-
#
|
|
669
|
-
#
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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"
|