zexus 1.6.8 → 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.
Files changed (177) hide show
  1. package/README.md +12 -5
  2. package/package.json +1 -1
  3. package/src/__init__.py +7 -0
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
  6. package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
  7. package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
  8. package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
  9. package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
  10. package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
  11. package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
  12. package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
  13. package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
  14. package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
  15. package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
  16. package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
  17. package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
  18. package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  19. package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
  20. package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
  22. package/src/zexus/advanced_types.py +17 -2
  23. package/src/zexus/blockchain/__init__.py +411 -0
  24. package/src/zexus/blockchain/accelerator.py +1160 -0
  25. package/src/zexus/blockchain/chain.py +660 -0
  26. package/src/zexus/blockchain/consensus.py +821 -0
  27. package/src/zexus/blockchain/contract_vm.py +1019 -0
  28. package/src/zexus/blockchain/crypto.py +79 -14
  29. package/src/zexus/blockchain/events.py +526 -0
  30. package/src/zexus/blockchain/loadtest.py +721 -0
  31. package/src/zexus/blockchain/monitoring.py +350 -0
  32. package/src/zexus/blockchain/mpt.py +716 -0
  33. package/src/zexus/blockchain/multichain.py +951 -0
  34. package/src/zexus/blockchain/multiprocess_executor.py +338 -0
  35. package/src/zexus/blockchain/network.py +886 -0
  36. package/src/zexus/blockchain/node.py +666 -0
  37. package/src/zexus/blockchain/rpc.py +1203 -0
  38. package/src/zexus/blockchain/rust_bridge.py +421 -0
  39. package/src/zexus/blockchain/storage.py +423 -0
  40. package/src/zexus/blockchain/tokens.py +750 -0
  41. package/src/zexus/blockchain/upgradeable.py +1004 -0
  42. package/src/zexus/blockchain/verification.py +1602 -0
  43. package/src/zexus/blockchain/wallet.py +621 -0
  44. package/src/zexus/capability_system.py +184 -9
  45. package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
  46. package/src/zexus/cli/main.py +383 -34
  47. package/src/zexus/cli/zpm.py +1 -1
  48. package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
  49. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  50. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  51. package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
  52. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  53. package/src/zexus/compiler/bytecode.py +124 -7
  54. package/src/zexus/compiler/compat_runtime.py +6 -2
  55. package/src/zexus/compiler/lexer.py +16 -5
  56. package/src/zexus/compiler/parser.py +108 -7
  57. package/src/zexus/compiler/semantic.py +18 -19
  58. package/src/zexus/compiler/zexus_ast.py +26 -1
  59. package/src/zexus/concurrency_system.py +79 -0
  60. package/src/zexus/config.py +54 -0
  61. package/src/zexus/crypto_bridge.py +244 -8
  62. package/src/zexus/dap/__init__.py +10 -0
  63. package/src/zexus/dap/__main__.py +4 -0
  64. package/src/zexus/dap/dap_server.py +391 -0
  65. package/src/zexus/dap/debug_engine.py +298 -0
  66. package/src/zexus/environment.py +112 -9
  67. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  68. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  69. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  70. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  71. package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
  72. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  73. package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
  74. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  75. package/src/zexus/evaluator/bytecode_compiler.py +457 -37
  76. package/src/zexus/evaluator/core.py +644 -50
  77. package/src/zexus/evaluator/expressions.py +358 -62
  78. package/src/zexus/evaluator/functions.py +458 -20
  79. package/src/zexus/evaluator/resource_limiter.py +4 -4
  80. package/src/zexus/evaluator/statements.py +774 -122
  81. package/src/zexus/evaluator/unified_execution.py +573 -72
  82. package/src/zexus/evaluator/utils.py +14 -2
  83. package/src/zexus/evaluator_original.py +1 -1
  84. package/src/zexus/event_loop.py +186 -0
  85. package/src/zexus/lexer.py +742 -458
  86. package/src/zexus/lsp/__init__.py +1 -1
  87. package/src/zexus/lsp/definition_provider.py +163 -9
  88. package/src/zexus/lsp/server.py +22 -8
  89. package/src/zexus/lsp/symbol_provider.py +182 -9
  90. package/src/zexus/module_cache.py +239 -9
  91. package/src/zexus/module_manager.py +129 -1
  92. package/src/zexus/object.py +76 -6
  93. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  94. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  95. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  96. package/src/zexus/parser/parser.py +1349 -408
  97. package/src/zexus/parser/strategy_context.py +755 -58
  98. package/src/zexus/parser/strategy_structural.py +121 -21
  99. package/src/zexus/persistence.py +15 -1
  100. package/src/zexus/renderer/__init__.py +61 -0
  101. package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
  103. package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
  104. package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
  105. package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
  106. package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
  107. package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
  108. package/src/zexus/renderer/backend.py +261 -0
  109. package/src/zexus/renderer/canvas.py +78 -0
  110. package/src/zexus/renderer/color_system.py +201 -0
  111. package/src/zexus/renderer/graphics.py +31 -0
  112. package/src/zexus/renderer/layout.py +222 -0
  113. package/src/zexus/renderer/main_renderer.py +66 -0
  114. package/src/zexus/renderer/painter.py +30 -0
  115. package/src/zexus/renderer/tk_backend.py +208 -0
  116. package/src/zexus/renderer/web_backend.py +260 -0
  117. package/src/zexus/runtime/__init__.py +10 -2
  118. package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  119. package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
  120. package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
  121. package/src/zexus/runtime/file_flags.py +137 -0
  122. package/src/zexus/runtime/load_manager.py +368 -0
  123. package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
  124. package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
  125. package/src/zexus/security.py +424 -34
  126. package/src/zexus/stdlib/fs.py +23 -18
  127. package/src/zexus/stdlib/http.py +289 -186
  128. package/src/zexus/stdlib/sockets.py +207 -163
  129. package/src/zexus/stdlib/websockets.py +282 -0
  130. package/src/zexus/stdlib_integration.py +369 -2
  131. package/src/zexus/strategy_recovery.py +6 -3
  132. package/src/zexus/type_checker.py +423 -0
  133. package/src/zexus/virtual_filesystem.py +189 -2
  134. package/src/zexus/vm/__init__.py +113 -3
  135. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  136. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  137. package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
  138. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  139. package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
  140. package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
  141. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  142. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  143. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  144. package/src/zexus/vm/async_optimizer.py +80 -6
  145. package/src/zexus/vm/binary_bytecode.py +659 -0
  146. package/src/zexus/vm/bytecode.py +59 -11
  147. package/src/zexus/vm/bytecode_converter.py +26 -12
  148. package/src/zexus/vm/cabi.c +1985 -0
  149. package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
  150. package/src/zexus/vm/cabi.h +127 -0
  151. package/src/zexus/vm/cache.py +561 -17
  152. package/src/zexus/vm/compiler.py +818 -51
  153. package/src/zexus/vm/fastops.c +15743 -0
  154. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  155. package/src/zexus/vm/fastops.pyx +288 -0
  156. package/src/zexus/vm/gas_metering.py +50 -9
  157. package/src/zexus/vm/jit.py +364 -20
  158. package/src/zexus/vm/native_jit_backend.py +1816 -0
  159. package/src/zexus/vm/native_runtime.cpp +1388 -0
  160. package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
  161. package/src/zexus/vm/optimizer.py +161 -11
  162. package/src/zexus/vm/parallel_vm.py +140 -45
  163. package/src/zexus/vm/peephole_optimizer.py +82 -4
  164. package/src/zexus/vm/profiler.py +38 -18
  165. package/src/zexus/vm/register_allocator.py +16 -5
  166. package/src/zexus/vm/register_vm.py +8 -5
  167. package/src/zexus/vm/vm.py +3581 -531
  168. package/src/zexus/vm/wasm_compiler.py +658 -0
  169. package/src/zexus/zexus_ast.py +137 -11
  170. package/src/zexus/zexus_token.py +16 -5
  171. package/src/zexus/zpm/installer.py +55 -15
  172. package/src/zexus/zpm/package_manager.py +1 -1
  173. package/src/zexus/zpm/registry.py +257 -28
  174. package/src/zexus.egg-info/PKG-INFO +16 -6
  175. package/src/zexus.egg-info/SOURCES.txt +129 -17
  176. package/src/zexus.egg-info/entry_points.txt +1 -0
  177. 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()