zexus 1.7.1 → 1.7.2
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.
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/__init__.py +7 -0
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +17 -2
- package/src/zexus/blockchain/__init__.py +411 -0
- package/src/zexus/blockchain/accelerator.py +1160 -0
- package/src/zexus/blockchain/chain.py +660 -0
- package/src/zexus/blockchain/consensus.py +821 -0
- package/src/zexus/blockchain/contract_vm.py +1019 -0
- package/src/zexus/blockchain/crypto.py +79 -14
- package/src/zexus/blockchain/events.py +526 -0
- package/src/zexus/blockchain/loadtest.py +721 -0
- package/src/zexus/blockchain/monitoring.py +350 -0
- package/src/zexus/blockchain/mpt.py +716 -0
- package/src/zexus/blockchain/multichain.py +951 -0
- package/src/zexus/blockchain/multiprocess_executor.py +338 -0
- package/src/zexus/blockchain/network.py +886 -0
- package/src/zexus/blockchain/node.py +666 -0
- package/src/zexus/blockchain/rpc.py +1203 -0
- package/src/zexus/blockchain/rust_bridge.py +421 -0
- package/src/zexus/blockchain/storage.py +423 -0
- package/src/zexus/blockchain/tokens.py +750 -0
- package/src/zexus/blockchain/upgradeable.py +1004 -0
- package/src/zexus/blockchain/verification.py +1602 -0
- package/src/zexus/blockchain/wallet.py +621 -0
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +300 -20
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/lexer.py +10 -5
- package/src/zexus/concurrency_system.py +79 -0
- package/src/zexus/config.py +54 -0
- package/src/zexus/crypto_bridge.py +244 -8
- package/src/zexus/dap/__init__.py +10 -0
- package/src/zexus/dap/__main__.py +4 -0
- package/src/zexus/dap/dap_server.py +391 -0
- package/src/zexus/dap/debug_engine.py +298 -0
- package/src/zexus/environment.py +10 -1
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +441 -37
- package/src/zexus/evaluator/core.py +560 -49
- package/src/zexus/evaluator/expressions.py +122 -49
- package/src/zexus/evaluator/functions.py +417 -16
- package/src/zexus/evaluator/statements.py +521 -118
- package/src/zexus/evaluator/unified_execution.py +573 -72
- package/src/zexus/evaluator/utils.py +14 -2
- package/src/zexus/event_loop.py +186 -0
- package/src/zexus/lexer.py +742 -486
- package/src/zexus/lsp/__init__.py +1 -1
- package/src/zexus/lsp/definition_provider.py +163 -9
- package/src/zexus/lsp/server.py +22 -8
- package/src/zexus/lsp/symbol_provider.py +182 -9
- package/src/zexus/module_cache.py +237 -9
- package/src/zexus/object.py +64 -6
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/parser.py +786 -285
- package/src/zexus/parser/strategy_context.py +407 -66
- package/src/zexus/parser/strategy_structural.py +117 -19
- package/src/zexus/persistence.py +15 -1
- package/src/zexus/renderer/__init__.py +15 -0
- package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
- package/src/zexus/renderer/tk_backend.py +208 -0
- package/src/zexus/renderer/web_backend.py +260 -0
- package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
- package/src/zexus/runtime/file_flags.py +137 -0
- package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
- package/src/zexus/security.py +424 -34
- package/src/zexus/stdlib/fs.py +23 -18
- package/src/zexus/stdlib/http.py +289 -186
- package/src/zexus/stdlib/sockets.py +207 -163
- package/src/zexus/stdlib/websockets.py +282 -0
- package/src/zexus/stdlib_integration.py +369 -2
- package/src/zexus/strategy_recovery.py +6 -3
- package/src/zexus/type_checker.py +423 -0
- package/src/zexus/virtual_filesystem.py +189 -2
- package/src/zexus/vm/__init__.py +113 -3
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +14 -1
- package/src/zexus/vm/binary_bytecode.py +659 -0
- package/src/zexus/vm/bytecode.py +28 -1
- package/src/zexus/vm/bytecode_converter.py +26 -12
- package/src/zexus/vm/cabi.c +1985 -0
- package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/cabi.h +127 -0
- package/src/zexus/vm/cache.py +557 -17
- package/src/zexus/vm/compiler.py +703 -5
- package/src/zexus/vm/fastops.c +15743 -0
- package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/fastops.pyx +288 -0
- package/src/zexus/vm/gas_metering.py +50 -9
- package/src/zexus/vm/jit.py +83 -2
- package/src/zexus/vm/native_jit_backend.py +1816 -0
- package/src/zexus/vm/native_runtime.cpp +1388 -0
- package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/optimizer.py +161 -11
- package/src/zexus/vm/parallel_vm.py +118 -42
- package/src/zexus/vm/peephole_optimizer.py +82 -4
- package/src/zexus/vm/profiler.py +38 -18
- package/src/zexus/vm/register_allocator.py +16 -5
- package/src/zexus/vm/register_vm.py +8 -5
- package/src/zexus/vm/vm.py +3411 -573
- package/src/zexus/vm/wasm_compiler.py +658 -0
- package/src/zexus/zexus_ast.py +63 -11
- package/src/zexus/zexus_token.py +13 -5
- package/src/zexus/zpm/installer.py +55 -15
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus/zpm/registry.py +257 -28
- package/src/zexus.egg-info/PKG-INFO +7 -4
- package/src/zexus.egg-info/SOURCES.txt +116 -9
- package/src/zexus.egg-info/entry_points.txt +1 -0
- package/src/zexus.egg-info/requires.txt +4 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Zexus Blockchain — Production Monitoring & Metrics
|
|
3
|
+
====================================================
|
|
4
|
+
|
|
5
|
+
Real-time metrics collection for blockchain node operators.
|
|
6
|
+
|
|
7
|
+
Tracks:
|
|
8
|
+
* **Block metrics** — block time, height, size, gas per block
|
|
9
|
+
* **Transaction metrics** — TPS, latency, success / fail rates
|
|
10
|
+
* **Peer metrics** — peer count, message rates, banned peers
|
|
11
|
+
* **Resource metrics** — mempool depth, storage size, cache hits
|
|
12
|
+
* **Consensus metrics** — round time, finality latency
|
|
13
|
+
|
|
14
|
+
Exposes metrics in two ways:
|
|
15
|
+
|
|
16
|
+
1. **Programmatic** — ``NodeMetrics.snapshot()`` returns a dict.
|
|
17
|
+
2. **HTTP endpoint** — ``MetricsServer`` serves ``/metrics`` in
|
|
18
|
+
Prometheus-compatible text format and ``/metrics/json`` as JSON.
|
|
19
|
+
|
|
20
|
+
Usage::
|
|
21
|
+
|
|
22
|
+
from zexus.blockchain.monitoring import NodeMetrics, MetricsServer
|
|
23
|
+
|
|
24
|
+
metrics = NodeMetrics()
|
|
25
|
+
metrics.record_block(block)
|
|
26
|
+
metrics.record_transaction(receipt)
|
|
27
|
+
|
|
28
|
+
# Optional: start HTTP metrics endpoint
|
|
29
|
+
server = MetricsServer(metrics, port=9100)
|
|
30
|
+
await server.start()
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import asyncio
|
|
36
|
+
import json
|
|
37
|
+
import logging
|
|
38
|
+
import time
|
|
39
|
+
import threading
|
|
40
|
+
from collections import deque
|
|
41
|
+
from dataclasses import dataclass, field
|
|
42
|
+
from typing import Any, Deque, Dict, List, Optional
|
|
43
|
+
|
|
44
|
+
logger = logging.getLogger("zexus.blockchain.monitoring")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ── Core Metrics ───────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
class NodeMetrics:
|
|
50
|
+
"""Lightweight, thread-safe metrics collector for a blockchain node.
|
|
51
|
+
|
|
52
|
+
All public methods are safe to call from any thread.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, window: int = 100):
|
|
56
|
+
"""
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
window : int
|
|
60
|
+
Number of recent data-points to keep for rolling averages.
|
|
61
|
+
"""
|
|
62
|
+
self._lock = threading.Lock()
|
|
63
|
+
self._window = window
|
|
64
|
+
|
|
65
|
+
# Block metrics
|
|
66
|
+
self.blocks_produced: int = 0
|
|
67
|
+
self.current_height: int = 0
|
|
68
|
+
self._block_times: Deque[float] = deque(maxlen=window)
|
|
69
|
+
self._block_gas: Deque[int] = deque(maxlen=window)
|
|
70
|
+
self._block_sizes: Deque[int] = deque(maxlen=window)
|
|
71
|
+
self._last_block_time: float = 0.0
|
|
72
|
+
|
|
73
|
+
# Transaction metrics
|
|
74
|
+
self.tx_total: int = 0
|
|
75
|
+
self.tx_succeeded: int = 0
|
|
76
|
+
self.tx_failed: int = 0
|
|
77
|
+
self._tx_latencies: Deque[float] = deque(maxlen=window)
|
|
78
|
+
|
|
79
|
+
# Peer metrics
|
|
80
|
+
self.peer_count: int = 0
|
|
81
|
+
self.peers_banned: int = 0
|
|
82
|
+
self.messages_in: int = 0
|
|
83
|
+
self.messages_out: int = 0
|
|
84
|
+
|
|
85
|
+
# Mempool
|
|
86
|
+
self.mempool_depth: int = 0
|
|
87
|
+
self.mempool_bytes: int = 0
|
|
88
|
+
|
|
89
|
+
# Consensus
|
|
90
|
+
self._consensus_rounds: Deque[float] = deque(maxlen=window)
|
|
91
|
+
self._finality_latencies: Deque[float] = deque(maxlen=window)
|
|
92
|
+
|
|
93
|
+
# Uptime
|
|
94
|
+
self._start_time: float = time.time()
|
|
95
|
+
|
|
96
|
+
# ── Recording methods ──────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
def record_block(self, block: Any) -> None:
|
|
99
|
+
"""Record a newly produced/received block."""
|
|
100
|
+
now = time.time()
|
|
101
|
+
with self._lock:
|
|
102
|
+
self.blocks_produced += 1
|
|
103
|
+
height = getattr(block, "header", None)
|
|
104
|
+
if height is not None:
|
|
105
|
+
height = getattr(height, "height", 0)
|
|
106
|
+
else:
|
|
107
|
+
height = 0
|
|
108
|
+
self.current_height = max(self.current_height, height)
|
|
109
|
+
|
|
110
|
+
if self._last_block_time > 0:
|
|
111
|
+
self._block_times.append(now - self._last_block_time)
|
|
112
|
+
self._last_block_time = now
|
|
113
|
+
|
|
114
|
+
# Gas / size
|
|
115
|
+
txs = getattr(block, "transactions", [])
|
|
116
|
+
tx_count = len(txs) if isinstance(txs, list) else 0
|
|
117
|
+
self._block_sizes.append(tx_count)
|
|
118
|
+
gas_total = sum(
|
|
119
|
+
getattr(tx, "gas_limit", 0) for tx in (txs or [])
|
|
120
|
+
)
|
|
121
|
+
self._block_gas.append(gas_total)
|
|
122
|
+
|
|
123
|
+
def record_transaction(self, receipt: Any) -> None:
|
|
124
|
+
"""Record a transaction result."""
|
|
125
|
+
with self._lock:
|
|
126
|
+
self.tx_total += 1
|
|
127
|
+
success = False
|
|
128
|
+
if isinstance(receipt, dict):
|
|
129
|
+
success = receipt.get("success", False)
|
|
130
|
+
latency = receipt.get("latency", 0.0)
|
|
131
|
+
else:
|
|
132
|
+
success = getattr(receipt, "success", False)
|
|
133
|
+
latency = getattr(receipt, "latency", 0.0)
|
|
134
|
+
|
|
135
|
+
if success:
|
|
136
|
+
self.tx_succeeded += 1
|
|
137
|
+
else:
|
|
138
|
+
self.tx_failed += 1
|
|
139
|
+
|
|
140
|
+
if latency > 0:
|
|
141
|
+
self._tx_latencies.append(latency)
|
|
142
|
+
|
|
143
|
+
def record_peer_count(self, count: int, banned: int = 0) -> None:
|
|
144
|
+
with self._lock:
|
|
145
|
+
self.peer_count = count
|
|
146
|
+
self.peers_banned = banned
|
|
147
|
+
|
|
148
|
+
def record_message(self, direction: str = "in") -> None:
|
|
149
|
+
with self._lock:
|
|
150
|
+
if direction == "in":
|
|
151
|
+
self.messages_in += 1
|
|
152
|
+
else:
|
|
153
|
+
self.messages_out += 1
|
|
154
|
+
|
|
155
|
+
def record_mempool(self, depth: int, size_bytes: int = 0) -> None:
|
|
156
|
+
with self._lock:
|
|
157
|
+
self.mempool_depth = depth
|
|
158
|
+
self.mempool_bytes = size_bytes
|
|
159
|
+
|
|
160
|
+
def record_consensus_round(self, duration: float) -> None:
|
|
161
|
+
with self._lock:
|
|
162
|
+
self._consensus_rounds.append(duration)
|
|
163
|
+
|
|
164
|
+
def record_finality(self, latency: float) -> None:
|
|
165
|
+
with self._lock:
|
|
166
|
+
self._finality_latencies.append(latency)
|
|
167
|
+
|
|
168
|
+
# ── Snapshot ───────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
def snapshot(self) -> Dict[str, Any]:
|
|
171
|
+
"""Return a point-in-time snapshot of all metrics."""
|
|
172
|
+
with self._lock:
|
|
173
|
+
uptime = time.time() - self._start_time
|
|
174
|
+
avg_block_time = (
|
|
175
|
+
sum(self._block_times) / len(self._block_times)
|
|
176
|
+
if self._block_times else 0.0
|
|
177
|
+
)
|
|
178
|
+
avg_block_gas = (
|
|
179
|
+
sum(self._block_gas) / len(self._block_gas)
|
|
180
|
+
if self._block_gas else 0.0
|
|
181
|
+
)
|
|
182
|
+
avg_block_size = (
|
|
183
|
+
sum(self._block_sizes) / len(self._block_sizes)
|
|
184
|
+
if self._block_sizes else 0.0
|
|
185
|
+
)
|
|
186
|
+
avg_tx_latency = (
|
|
187
|
+
sum(self._tx_latencies) / len(self._tx_latencies)
|
|
188
|
+
if self._tx_latencies else 0.0
|
|
189
|
+
)
|
|
190
|
+
tps = self.tx_total / uptime if uptime > 0 else 0.0
|
|
191
|
+
avg_consensus = (
|
|
192
|
+
sum(self._consensus_rounds) / len(self._consensus_rounds)
|
|
193
|
+
if self._consensus_rounds else 0.0
|
|
194
|
+
)
|
|
195
|
+
avg_finality = (
|
|
196
|
+
sum(self._finality_latencies) / len(self._finality_latencies)
|
|
197
|
+
if self._finality_latencies else 0.0
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
"uptime_seconds": round(uptime, 2),
|
|
202
|
+
"block": {
|
|
203
|
+
"height": self.current_height,
|
|
204
|
+
"total_produced": self.blocks_produced,
|
|
205
|
+
"avg_block_time": round(avg_block_time, 3),
|
|
206
|
+
"avg_gas_per_block": round(avg_block_gas, 1),
|
|
207
|
+
"avg_txs_per_block": round(avg_block_size, 1),
|
|
208
|
+
},
|
|
209
|
+
"transaction": {
|
|
210
|
+
"total": self.tx_total,
|
|
211
|
+
"succeeded": self.tx_succeeded,
|
|
212
|
+
"failed": self.tx_failed,
|
|
213
|
+
"success_rate": round(
|
|
214
|
+
self.tx_succeeded / self.tx_total * 100, 1
|
|
215
|
+
) if self.tx_total > 0 else 100.0,
|
|
216
|
+
"avg_latency_ms": round(avg_tx_latency * 1000, 2),
|
|
217
|
+
"tps": round(tps, 2),
|
|
218
|
+
},
|
|
219
|
+
"peer": {
|
|
220
|
+
"connected": self.peer_count,
|
|
221
|
+
"banned": self.peers_banned,
|
|
222
|
+
"messages_in": self.messages_in,
|
|
223
|
+
"messages_out": self.messages_out,
|
|
224
|
+
},
|
|
225
|
+
"mempool": {
|
|
226
|
+
"pending_txs": self.mempool_depth,
|
|
227
|
+
"size_bytes": self.mempool_bytes,
|
|
228
|
+
},
|
|
229
|
+
"consensus": {
|
|
230
|
+
"avg_round_time": round(avg_consensus, 3),
|
|
231
|
+
"avg_finality_latency": round(avg_finality, 3),
|
|
232
|
+
},
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# ── Prometheus-compatible text format ──────────────────────────
|
|
236
|
+
|
|
237
|
+
def prometheus_text(self) -> str:
|
|
238
|
+
"""Render metrics in Prometheus text exposition format."""
|
|
239
|
+
s = self.snapshot()
|
|
240
|
+
lines: List[str] = []
|
|
241
|
+
|
|
242
|
+
def _g(name: str, value: Any, help_text: str = ""):
|
|
243
|
+
if help_text:
|
|
244
|
+
lines.append(f"# HELP {name} {help_text}")
|
|
245
|
+
lines.append(f"# TYPE {name} gauge")
|
|
246
|
+
lines.append(f"{name} {value}")
|
|
247
|
+
|
|
248
|
+
_g("zexus_block_height", s["block"]["height"], "Current chain height")
|
|
249
|
+
_g("zexus_blocks_produced_total", s["block"]["total_produced"], "Total blocks produced")
|
|
250
|
+
_g("zexus_avg_block_time_seconds", s["block"]["avg_block_time"], "Average block time")
|
|
251
|
+
_g("zexus_avg_gas_per_block", s["block"]["avg_gas_per_block"], "Avg gas usage per block")
|
|
252
|
+
_g("zexus_tx_total", s["transaction"]["total"], "Total transactions processed")
|
|
253
|
+
_g("zexus_tx_succeeded", s["transaction"]["succeeded"], "Successful transactions")
|
|
254
|
+
_g("zexus_tx_failed", s["transaction"]["failed"], "Failed transactions")
|
|
255
|
+
_g("zexus_tx_success_rate", s["transaction"]["success_rate"], "Tx success rate %")
|
|
256
|
+
_g("zexus_tps", s["transaction"]["tps"], "Current transactions per second")
|
|
257
|
+
_g("zexus_avg_tx_latency_ms", s["transaction"]["avg_latency_ms"], "Avg tx latency ms")
|
|
258
|
+
_g("zexus_peers_connected", s["peer"]["connected"], "Connected peers")
|
|
259
|
+
_g("zexus_peers_banned", s["peer"]["banned"], "Banned peers")
|
|
260
|
+
_g("zexus_messages_in_total", s["peer"]["messages_in"], "Inbound messages")
|
|
261
|
+
_g("zexus_messages_out_total", s["peer"]["messages_out"], "Outbound messages")
|
|
262
|
+
_g("zexus_mempool_depth", s["mempool"]["pending_txs"], "Pending mempool txs")
|
|
263
|
+
_g("zexus_mempool_bytes", s["mempool"]["size_bytes"], "Mempool size bytes")
|
|
264
|
+
_g("zexus_avg_consensus_round_seconds", s["consensus"]["avg_round_time"], "Avg consensus round")
|
|
265
|
+
_g("zexus_avg_finality_latency_seconds", s["consensus"]["avg_finality_latency"], "Avg finality latency")
|
|
266
|
+
_g("zexus_uptime_seconds", s["uptime_seconds"], "Node uptime")
|
|
267
|
+
|
|
268
|
+
return "\n".join(lines) + "\n"
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# ── HTTP Metrics Server ───────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
class MetricsServer:
|
|
274
|
+
"""Lightweight HTTP server that exposes node metrics.
|
|
275
|
+
|
|
276
|
+
Endpoints:
|
|
277
|
+
* ``GET /metrics`` — Prometheus text format
|
|
278
|
+
* ``GET /metrics/json`` — JSON snapshot
|
|
279
|
+
* ``GET /health`` — Simple health check
|
|
280
|
+
|
|
281
|
+
Usage::
|
|
282
|
+
|
|
283
|
+
server = MetricsServer(metrics, port=9100)
|
|
284
|
+
await server.start()
|
|
285
|
+
# ...
|
|
286
|
+
await server.stop()
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
def __init__(self, metrics: NodeMetrics, host: str = "0.0.0.0", port: int = 9100):
|
|
290
|
+
self.metrics = metrics
|
|
291
|
+
self.host = host
|
|
292
|
+
self.port = port
|
|
293
|
+
self._server: Optional[asyncio.AbstractServer] = None
|
|
294
|
+
|
|
295
|
+
async def start(self) -> None:
|
|
296
|
+
self._server = await asyncio.start_server(
|
|
297
|
+
self._handle_request, self.host, self.port
|
|
298
|
+
)
|
|
299
|
+
logger.info("Metrics server listening on %s:%d", self.host, self.port)
|
|
300
|
+
|
|
301
|
+
async def stop(self) -> None:
|
|
302
|
+
if self._server:
|
|
303
|
+
self._server.close()
|
|
304
|
+
await self._server.wait_closed()
|
|
305
|
+
logger.info("Metrics server stopped")
|
|
306
|
+
|
|
307
|
+
async def _handle_request(
|
|
308
|
+
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
|
|
309
|
+
) -> None:
|
|
310
|
+
try:
|
|
311
|
+
data = await asyncio.wait_for(reader.read(4096), timeout=5.0)
|
|
312
|
+
request = data.decode("utf-8", errors="replace")
|
|
313
|
+
first_line = request.split("\r\n")[0] if request else ""
|
|
314
|
+
path = first_line.split(" ")[1] if len(first_line.split(" ")) > 1 else "/"
|
|
315
|
+
except Exception:
|
|
316
|
+
writer.close()
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
if path == "/metrics":
|
|
320
|
+
body = self.metrics.prometheus_text()
|
|
321
|
+
content_type = "text/plain; version=0.0.4; charset=utf-8"
|
|
322
|
+
elif path == "/metrics/json":
|
|
323
|
+
body = json.dumps(self.metrics.snapshot(), indent=2)
|
|
324
|
+
content_type = "application/json"
|
|
325
|
+
elif path == "/health":
|
|
326
|
+
body = json.dumps({"status": "ok"})
|
|
327
|
+
content_type = "application/json"
|
|
328
|
+
else:
|
|
329
|
+
body = "Not Found"
|
|
330
|
+
content_type = "text/plain"
|
|
331
|
+
response = (
|
|
332
|
+
f"HTTP/1.1 404 Not Found\r\n"
|
|
333
|
+
f"Content-Type: {content_type}\r\n"
|
|
334
|
+
f"Content-Length: {len(body)}\r\n"
|
|
335
|
+
f"Connection: close\r\n\r\n{body}"
|
|
336
|
+
)
|
|
337
|
+
writer.write(response.encode())
|
|
338
|
+
await writer.drain()
|
|
339
|
+
writer.close()
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
response = (
|
|
343
|
+
f"HTTP/1.1 200 OK\r\n"
|
|
344
|
+
f"Content-Type: {content_type}\r\n"
|
|
345
|
+
f"Content-Length: {len(body)}\r\n"
|
|
346
|
+
f"Connection: close\r\n\r\n{body}"
|
|
347
|
+
)
|
|
348
|
+
writer.write(response.encode())
|
|
349
|
+
await writer.drain()
|
|
350
|
+
writer.close()
|