mplang-nightly 0.1.dev268__py3-none-any.whl → 0.1.dev270__py3-none-any.whl
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.
- mplang/__init__.py +391 -17
- mplang/{v2/backends → backends}/__init__.py +9 -7
- mplang/{v2/backends → backends}/bfv_impl.py +6 -6
- mplang/{v2/backends → backends}/crypto_impl.py +6 -6
- mplang/{v2/backends → backends}/field_impl.py +5 -5
- mplang/{v2/backends → backends}/func_impl.py +4 -4
- mplang/{v2/backends → backends}/phe_impl.py +3 -3
- mplang/{v2/backends → backends}/simp_design.md +1 -1
- mplang/{v2/backends → backends}/simp_driver/__init__.py +5 -5
- mplang/{v2/backends → backends}/simp_driver/http.py +8 -8
- mplang/{v2/backends → backends}/simp_driver/mem.py +9 -9
- mplang/{v2/backends → backends}/simp_driver/ops.py +4 -4
- mplang/{v2/backends → backends}/simp_driver/state.py +2 -2
- mplang/{v2/backends → backends}/simp_driver/values.py +2 -2
- mplang/{v2/backends → backends}/simp_worker/__init__.py +3 -3
- mplang/{v2/backends → backends}/simp_worker/http.py +10 -10
- mplang/{v2/backends → backends}/simp_worker/mem.py +1 -1
- mplang/{v2/backends → backends}/simp_worker/ops.py +5 -5
- mplang/{v2/backends → backends}/simp_worker/state.py +2 -4
- mplang/{v2/backends → backends}/spu_impl.py +8 -8
- mplang/{v2/backends → backends}/spu_state.py +4 -4
- mplang/{v2/backends → backends}/store_impl.py +3 -3
- mplang/{v2/backends → backends}/table_impl.py +8 -8
- mplang/{v2/backends → backends}/tee_impl.py +6 -6
- mplang/{v2/backends → backends}/tensor_impl.py +6 -6
- mplang/{v2/cli.py → cli.py} +9 -9
- mplang/{v2/cli_guide.md → cli_guide.md} +12 -12
- mplang/{v2/dialects → dialects}/__init__.py +5 -5
- mplang/{v2/dialects → dialects}/bfv.py +6 -6
- mplang/{v2/dialects → dialects}/crypto.py +5 -5
- mplang/{v2/dialects → dialects}/dtypes.py +2 -2
- mplang/{v2/dialects → dialects}/field.py +3 -3
- mplang/{v2/dialects → dialects}/func.py +2 -2
- mplang/{v2/dialects → dialects}/phe.py +6 -6
- mplang/{v2/dialects → dialects}/simp.py +6 -6
- mplang/{v2/dialects → dialects}/spu.py +7 -7
- mplang/{v2/dialects → dialects}/store.py +2 -2
- mplang/{v2/dialects → dialects}/table.py +3 -3
- mplang/{v2/dialects → dialects}/tee.py +6 -6
- mplang/{v2/dialects → dialects}/tensor.py +5 -5
- mplang/{v2/edsl → edsl}/__init__.py +3 -3
- mplang/{v2/edsl → edsl}/context.py +6 -6
- mplang/{v2/edsl → edsl}/graph.py +5 -5
- mplang/{v2/edsl → edsl}/jit.py +2 -2
- mplang/{v2/edsl → edsl}/object.py +1 -1
- mplang/{v2/edsl → edsl}/primitive.py +5 -5
- mplang/{v2/edsl → edsl}/printer.py +1 -1
- mplang/{v2/edsl → edsl}/serde.py +1 -1
- mplang/{v2/edsl → edsl}/tracer.py +7 -7
- mplang/{v2/edsl → edsl}/typing.py +1 -1
- mplang/{v2/kernels → kernels}/ldpc.cpp +13 -13
- mplang/{v2/kernels → kernels}/okvs.cpp +4 -4
- mplang/{v2/kernels → kernels}/okvs_opt.cpp +46 -31
- mplang/{v2/kernels → kernels}/py_kernels.py +1 -1
- mplang/{v2/libs → libs}/collective.py +5 -5
- mplang/{v2/libs → libs}/device/__init__.py +1 -1
- mplang/{v2/libs → libs}/device/api.py +12 -12
- mplang/{v2/libs → libs}/ml/__init__.py +1 -1
- mplang/{v2/libs → libs}/ml/sgb.py +4 -4
- mplang/{v2/libs → libs}/mpc/__init__.py +3 -3
- mplang/{v2/libs → libs}/mpc/_utils.py +2 -2
- mplang/{v2/libs → libs}/mpc/analytics/aggregation.py +1 -1
- mplang/{v2/libs → libs}/mpc/analytics/groupby.py +2 -2
- mplang/{v2/libs → libs}/mpc/analytics/permutation.py +3 -3
- mplang/{v2/libs → libs}/mpc/ot/base.py +3 -3
- mplang/{v2/libs → libs}/mpc/ot/extension.py +2 -2
- mplang/{v2/libs → libs}/mpc/ot/silent.py +4 -4
- mplang/{v2/libs → libs}/mpc/psi/cuckoo.py +3 -3
- mplang/{v2/libs → libs}/mpc/psi/okvs.py +1 -1
- mplang/{v2/libs → libs}/mpc/psi/okvs_gct.py +19 -13
- mplang/{v2/libs → libs}/mpc/psi/oprf.py +3 -3
- mplang/libs/mpc/psi/rr22.py +303 -0
- mplang/{v2/libs → libs}/mpc/psi/unbalanced.py +4 -4
- mplang/{v2/libs → libs}/mpc/vole/gilboa.py +3 -3
- mplang/{v2/libs → libs}/mpc/vole/ldpc.py +2 -2
- mplang/{v2/libs → libs}/mpc/vole/silver.py +6 -6
- mplang/{v2/runtime → runtime}/interpreter.py +11 -11
- mplang/{v2/runtime → runtime}/value.py +2 -2
- mplang/{v1/runtime → utils}/__init__.py +18 -15
- mplang/{v1/utils → utils}/func_utils.py +1 -1
- {mplang_nightly-0.1.dev268.dist-info → mplang_nightly-0.1.dev270.dist-info}/METADATA +2 -2
- mplang_nightly-0.1.dev270.dist-info/RECORD +102 -0
- mplang/v1/__init__.py +0 -157
- mplang/v1/_device.py +0 -602
- mplang/v1/analysis/__init__.py +0 -37
- mplang/v1/analysis/diagram.py +0 -567
- mplang/v1/core/__init__.py +0 -157
- mplang/v1/core/cluster.py +0 -343
- mplang/v1/core/comm.py +0 -281
- mplang/v1/core/context_mgr.py +0 -50
- mplang/v1/core/dtypes.py +0 -335
- mplang/v1/core/expr/__init__.py +0 -80
- mplang/v1/core/expr/ast.py +0 -542
- mplang/v1/core/expr/evaluator.py +0 -581
- mplang/v1/core/expr/printer.py +0 -285
- mplang/v1/core/expr/transformer.py +0 -141
- mplang/v1/core/expr/utils.py +0 -78
- mplang/v1/core/expr/visitor.py +0 -85
- mplang/v1/core/expr/walk.py +0 -387
- mplang/v1/core/interp.py +0 -160
- mplang/v1/core/mask.py +0 -325
- mplang/v1/core/mpir.py +0 -965
- mplang/v1/core/mpobject.py +0 -117
- mplang/v1/core/mptype.py +0 -407
- mplang/v1/core/pfunc.py +0 -130
- mplang/v1/core/primitive.py +0 -877
- mplang/v1/core/table.py +0 -218
- mplang/v1/core/tensor.py +0 -75
- mplang/v1/core/tracer.py +0 -383
- mplang/v1/host.py +0 -130
- mplang/v1/kernels/__init__.py +0 -41
- mplang/v1/kernels/base.py +0 -125
- mplang/v1/kernels/basic.py +0 -240
- mplang/v1/kernels/context.py +0 -369
- mplang/v1/kernels/crypto.py +0 -122
- mplang/v1/kernels/fhe.py +0 -858
- mplang/v1/kernels/mock_tee.py +0 -72
- mplang/v1/kernels/phe.py +0 -1864
- mplang/v1/kernels/spu.py +0 -341
- mplang/v1/kernels/sql_duckdb.py +0 -44
- mplang/v1/kernels/stablehlo.py +0 -90
- mplang/v1/kernels/value.py +0 -626
- mplang/v1/ops/__init__.py +0 -35
- mplang/v1/ops/base.py +0 -424
- mplang/v1/ops/basic.py +0 -294
- mplang/v1/ops/crypto.py +0 -262
- mplang/v1/ops/fhe.py +0 -272
- mplang/v1/ops/jax_cc.py +0 -147
- mplang/v1/ops/nnx_cc.py +0 -168
- mplang/v1/ops/phe.py +0 -216
- mplang/v1/ops/spu.py +0 -151
- mplang/v1/ops/sql_cc.py +0 -303
- mplang/v1/ops/tee.py +0 -36
- mplang/v1/protos/v1alpha1/mpir_pb2.py +0 -63
- mplang/v1/protos/v1alpha1/mpir_pb2.pyi +0 -557
- mplang/v1/protos/v1alpha1/value_pb2.py +0 -34
- mplang/v1/protos/v1alpha1/value_pb2.pyi +0 -169
- mplang/v1/runtime/channel.py +0 -230
- mplang/v1/runtime/cli.py +0 -451
- mplang/v1/runtime/client.py +0 -456
- mplang/v1/runtime/communicator.py +0 -131
- mplang/v1/runtime/data_providers.py +0 -303
- mplang/v1/runtime/driver.py +0 -324
- mplang/v1/runtime/exceptions.py +0 -27
- mplang/v1/runtime/http_api.md +0 -56
- mplang/v1/runtime/link_comm.py +0 -196
- mplang/v1/runtime/server.py +0 -501
- mplang/v1/runtime/session.py +0 -270
- mplang/v1/runtime/simulation.py +0 -324
- mplang/v1/simp/__init__.py +0 -13
- mplang/v1/simp/api.py +0 -353
- mplang/v1/simp/mpi.py +0 -131
- mplang/v1/simp/party.py +0 -225
- mplang/v1/simp/random.py +0 -120
- mplang/v1/simp/smpc.py +0 -238
- mplang/v1/utils/__init__.py +0 -13
- mplang/v1/utils/crypto.py +0 -32
- mplang/v1/utils/spu_utils.py +0 -130
- mplang/v1/utils/table_utils.py +0 -185
- mplang/v2/__init__.py +0 -424
- mplang/v2/libs/mpc/psi/rr22.py +0 -344
- mplang_nightly-0.1.dev268.dist-info/RECORD +0 -180
- /mplang/{v2/backends → backends}/channel.py +0 -0
- /mplang/{v2/edsl → edsl}/README.md +0 -0
- /mplang/{v2/edsl → edsl}/registry.py +0 -0
- /mplang/{v2/kernels → kernels}/Makefile +0 -0
- /mplang/{v2/kernels → kernels}/__init__.py +0 -0
- /mplang/{v2/kernels → kernels}/gf128.cpp +0 -0
- /mplang/{v2/libs → libs}/device/cluster.py +0 -0
- /mplang/{v2/libs → libs}/mpc/analytics/__init__.py +0 -0
- /mplang/{v2/libs → libs}/mpc/analytics/groupby.md +0 -0
- /mplang/{v2/libs → libs}/mpc/common/constants.py +0 -0
- /mplang/{v2/libs → libs}/mpc/ot/__init__.py +0 -0
- /mplang/{v2/libs → libs}/mpc/psi/__init__.py +0 -0
- /mplang/{v2/libs → libs}/mpc/vole/__init__.py +0 -0
- /mplang/{v2/runtime → runtime}/__init__.py +0 -0
- /mplang/{v2/runtime → runtime}/dialect_state.py +0 -0
- /mplang/{v2/runtime → runtime}/object_store.py +0 -0
- {mplang_nightly-0.1.dev268.dist-info → mplang_nightly-0.1.dev270.dist-info}/WHEEL +0 -0
- {mplang_nightly-0.1.dev268.dist-info → mplang_nightly-0.1.dev270.dist-info}/entry_points.txt +0 -0
- {mplang_nightly-0.1.dev268.dist-info → mplang_nightly-0.1.dev270.dist-info}/licenses/LICENSE +0 -0
mplang/v1/runtime/cli.py
DELETED
|
@@ -1,451 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# Copyright 2025 Ant Group Co., Ltd.
|
|
3
|
-
#
|
|
4
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
# you may not use this file except in compliance with the License.
|
|
6
|
-
# You may obtain a copy of the License at
|
|
7
|
-
#
|
|
8
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
#
|
|
10
|
-
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
# See the License for the specific language governing permissions and
|
|
14
|
-
# limitations under the License.
|
|
15
|
-
|
|
16
|
-
"""
|
|
17
|
-
Command-line interface for managing MPLang clusters.
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
import argparse
|
|
21
|
-
import asyncio
|
|
22
|
-
import multiprocessing
|
|
23
|
-
import sys
|
|
24
|
-
from typing import Any
|
|
25
|
-
|
|
26
|
-
import uvicorn
|
|
27
|
-
import yaml
|
|
28
|
-
|
|
29
|
-
from mplang.v1.core import ClusterSpec
|
|
30
|
-
from mplang.v1.runtime.client import HttpExecutorClient
|
|
31
|
-
from mplang.v1.runtime.server import app
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def load_config(config_path: str) -> ClusterSpec:
|
|
35
|
-
"""Load configuration from a YAML file."""
|
|
36
|
-
with open(config_path) as file:
|
|
37
|
-
conf = yaml.safe_load(file)
|
|
38
|
-
return ClusterSpec.from_dict(conf)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def run_server(port: int, node_id: str) -> None:
|
|
42
|
-
"""Run a uvicorn server on a specific port.
|
|
43
|
-
|
|
44
|
-
Args:
|
|
45
|
-
port: The port to run the server on
|
|
46
|
-
node_id: The ID of the node
|
|
47
|
-
"""
|
|
48
|
-
log_config = {
|
|
49
|
-
"version": 1,
|
|
50
|
-
"disable_existing_loggers": False,
|
|
51
|
-
"formatters": {
|
|
52
|
-
"default": {
|
|
53
|
-
"()": "uvicorn.logging.DefaultFormatter",
|
|
54
|
-
"fmt": f"%(levelname)s: [{node_id}] %(message)s",
|
|
55
|
-
"use_colors": None,
|
|
56
|
-
},
|
|
57
|
-
"access": {
|
|
58
|
-
"()": "uvicorn.logging.AccessFormatter",
|
|
59
|
-
"fmt": f'%(levelname)s: [{node_id}] %(client_addr)s - "%(request_line)s" %(status_code)s',
|
|
60
|
-
"use_colors": None,
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
"handlers": {
|
|
64
|
-
"default": {
|
|
65
|
-
"formatter": "default",
|
|
66
|
-
"class": "logging.StreamHandler",
|
|
67
|
-
"stream": "ext://sys.stderr",
|
|
68
|
-
},
|
|
69
|
-
"access": {
|
|
70
|
-
"formatter": "access",
|
|
71
|
-
"class": "logging.StreamHandler",
|
|
72
|
-
"stream": "ext://sys.stdout",
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
"loggers": {
|
|
76
|
-
"uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
|
77
|
-
"uvicorn.error": {"level": "INFO"},
|
|
78
|
-
"uvicorn.access": {
|
|
79
|
-
"handlers": ["access"],
|
|
80
|
-
"level": "INFO",
|
|
81
|
-
"propagate": False,
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
}
|
|
85
|
-
config = uvicorn.Config(
|
|
86
|
-
app,
|
|
87
|
-
host="127.0.0.1",
|
|
88
|
-
port=port,
|
|
89
|
-
log_config=log_config,
|
|
90
|
-
ws="none", # Disable websockets
|
|
91
|
-
)
|
|
92
|
-
server = uvicorn.Server(config)
|
|
93
|
-
server.run()
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def start_command(args: argparse.Namespace) -> int:
|
|
97
|
-
"""Handle the start command.
|
|
98
|
-
|
|
99
|
-
Args:
|
|
100
|
-
args: Parsed command line arguments
|
|
101
|
-
|
|
102
|
-
Returns:
|
|
103
|
-
Exit code (0 for success, non-zero for failure)
|
|
104
|
-
"""
|
|
105
|
-
try:
|
|
106
|
-
# Load configuration
|
|
107
|
-
cluster_spec = load_config(args.config)
|
|
108
|
-
nodes = cluster_spec.nodes
|
|
109
|
-
|
|
110
|
-
if not nodes:
|
|
111
|
-
print("No nodes defined in configuration")
|
|
112
|
-
return 1
|
|
113
|
-
|
|
114
|
-
# Find the endpoint for the specified node
|
|
115
|
-
node_id = args.node_id
|
|
116
|
-
if node_id not in nodes:
|
|
117
|
-
print(f"Node {node_id} not found in configuration")
|
|
118
|
-
return 1
|
|
119
|
-
|
|
120
|
-
endpoint = nodes[node_id].endpoint
|
|
121
|
-
# Extract port from endpoint (format: host:port)
|
|
122
|
-
port = int(endpoint.split(":")[-1])
|
|
123
|
-
|
|
124
|
-
print(f"Starting node {node_id} on port {port}...")
|
|
125
|
-
# Run the server directly (blocking)
|
|
126
|
-
run_server(port, node_id)
|
|
127
|
-
|
|
128
|
-
return 0
|
|
129
|
-
except Exception as e:
|
|
130
|
-
print(f"Error starting node: {e}")
|
|
131
|
-
return 1
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def start_cluster_command(args: argparse.Namespace) -> int:
|
|
135
|
-
"""Handle the start-cluster command.
|
|
136
|
-
|
|
137
|
-
Args:
|
|
138
|
-
args: Parsed command line arguments
|
|
139
|
-
|
|
140
|
-
Returns:
|
|
141
|
-
Exit code (0 for success, non-zero for failure)
|
|
142
|
-
"""
|
|
143
|
-
try:
|
|
144
|
-
# Load configuration
|
|
145
|
-
cluster_spec = load_config(args.config)
|
|
146
|
-
nodes = cluster_spec.nodes
|
|
147
|
-
|
|
148
|
-
if not nodes:
|
|
149
|
-
print("No nodes defined in configuration")
|
|
150
|
-
return 1
|
|
151
|
-
|
|
152
|
-
# Start a process for each node
|
|
153
|
-
processes = []
|
|
154
|
-
for node_id, node in nodes.items():
|
|
155
|
-
# Extract port from endpoint (format: host:port)
|
|
156
|
-
port = int(node.endpoint.split(":")[-1])
|
|
157
|
-
|
|
158
|
-
# Create and start process
|
|
159
|
-
process = multiprocessing.Process(target=run_server, args=(port, node_id))
|
|
160
|
-
process.start()
|
|
161
|
-
processes.append((node_id, process))
|
|
162
|
-
print(f"Started node {node_id} on port {port} (PID: {process.pid})")
|
|
163
|
-
|
|
164
|
-
print(f"Started {len(processes)} nodes in cluster")
|
|
165
|
-
print("Press Ctrl+C to stop all nodes")
|
|
166
|
-
|
|
167
|
-
# Wait for all processes to complete or for interruption
|
|
168
|
-
try:
|
|
169
|
-
for _, process in processes:
|
|
170
|
-
process.join()
|
|
171
|
-
except KeyboardInterrupt:
|
|
172
|
-
print("\nStopping all nodes...")
|
|
173
|
-
for _, process in processes:
|
|
174
|
-
if process.is_alive():
|
|
175
|
-
process.terminate()
|
|
176
|
-
process.join(timeout=5)
|
|
177
|
-
if process.is_alive():
|
|
178
|
-
process.kill()
|
|
179
|
-
print("All nodes stopped")
|
|
180
|
-
|
|
181
|
-
return 0
|
|
182
|
-
except Exception as e:
|
|
183
|
-
print(f"Error starting cluster: {e}")
|
|
184
|
-
return 1
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
def status_command(args: argparse.Namespace) -> int:
|
|
188
|
-
"""Handle the status command.
|
|
189
|
-
|
|
190
|
-
Args:
|
|
191
|
-
args: Parsed command line arguments
|
|
192
|
-
|
|
193
|
-
Returns:
|
|
194
|
-
Exit code (0 for success, non-zero for failure)
|
|
195
|
-
"""
|
|
196
|
-
|
|
197
|
-
async def _get_node_status(
|
|
198
|
-
node_id: str, endpoint: str, details: int = 0, timeout: int = 60
|
|
199
|
-
) -> dict[str, Any]:
|
|
200
|
-
"""Get status information for a single node.
|
|
201
|
-
|
|
202
|
-
Args:
|
|
203
|
-
node_id: Identifier for the node
|
|
204
|
-
endpoint: HTTP endpoint of the node
|
|
205
|
-
details: Verbosity level (0=basic, 1=-v, 2=-vv)
|
|
206
|
-
timeout: HTTP request timeout in seconds (default: 60)
|
|
207
|
-
"""
|
|
208
|
-
|
|
209
|
-
client = HttpExecutorClient(endpoint, timeout)
|
|
210
|
-
status: dict[str, Any] = {
|
|
211
|
-
"node_id": node_id,
|
|
212
|
-
"endpoint": client.endpoint, # Use the normalized endpoint from client
|
|
213
|
-
"healthy": False,
|
|
214
|
-
"sessions": [],
|
|
215
|
-
"error": None,
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
try:
|
|
219
|
-
# Check node health
|
|
220
|
-
status["healthy"] = await client.health_check()
|
|
221
|
-
|
|
222
|
-
if status["healthy"]:
|
|
223
|
-
# Get sessions on this node
|
|
224
|
-
sessions = await client.list_sessions()
|
|
225
|
-
status["sessions"] = sessions
|
|
226
|
-
|
|
227
|
-
# Get detailed session info based on verbosity level
|
|
228
|
-
# details=1 (-v): show session names and basic counts
|
|
229
|
-
# details=2 (-vv): show full computation and symbol lists
|
|
230
|
-
if details >= 1:
|
|
231
|
-
session_details = []
|
|
232
|
-
for session_name in sessions:
|
|
233
|
-
try:
|
|
234
|
-
# Get computations and symbols for each session
|
|
235
|
-
computations = await client.list_computations(session_name)
|
|
236
|
-
symbols = await client.list_symbols(session_name)
|
|
237
|
-
session_info = {
|
|
238
|
-
"name": session_name,
|
|
239
|
-
"computations": len(computations),
|
|
240
|
-
"symbols": len(symbols),
|
|
241
|
-
}
|
|
242
|
-
# Include full lists only at -vv level
|
|
243
|
-
if details >= 2:
|
|
244
|
-
session_info["computation_list"] = computations
|
|
245
|
-
session_info["symbol_list"] = symbols
|
|
246
|
-
session_details.append(session_info)
|
|
247
|
-
except Exception as e:
|
|
248
|
-
session_details.append({
|
|
249
|
-
"name": session_name,
|
|
250
|
-
"error": str(e),
|
|
251
|
-
})
|
|
252
|
-
status["session_details"] = session_details
|
|
253
|
-
|
|
254
|
-
except Exception as e:
|
|
255
|
-
status["error"] = str(e)
|
|
256
|
-
|
|
257
|
-
finally:
|
|
258
|
-
await client.close()
|
|
259
|
-
|
|
260
|
-
return status
|
|
261
|
-
|
|
262
|
-
async def _collect_cluster_status(
|
|
263
|
-
nodes: dict[str, str], details: int = 0
|
|
264
|
-
) -> list[dict[str, Any] | BaseException]:
|
|
265
|
-
"""Collect status from all nodes concurrently.
|
|
266
|
-
|
|
267
|
-
Args:
|
|
268
|
-
nodes: Dictionary mapping node IDs to their HTTP endpoints
|
|
269
|
-
details: Verbosity level (0=basic, 1=-v, 2=-vv)
|
|
270
|
-
|
|
271
|
-
Returns:
|
|
272
|
-
List of status dictionaries or exceptions for each node
|
|
273
|
-
"""
|
|
274
|
-
tasks = [
|
|
275
|
-
_get_node_status(node_id, endpoint, details)
|
|
276
|
-
for node_id, endpoint in nodes.items()
|
|
277
|
-
]
|
|
278
|
-
return await asyncio.gather(*tasks, return_exceptions=True)
|
|
279
|
-
|
|
280
|
-
try:
|
|
281
|
-
# Load configuration
|
|
282
|
-
cluster_spec = load_config(args.config)
|
|
283
|
-
nodes = cluster_spec.nodes
|
|
284
|
-
|
|
285
|
-
if not nodes:
|
|
286
|
-
print("No nodes defined in configuration")
|
|
287
|
-
return 1
|
|
288
|
-
|
|
289
|
-
node_addrs = {node_id: node.endpoint for node_id, node in nodes.items()}
|
|
290
|
-
|
|
291
|
-
# Collect status from all nodes
|
|
292
|
-
verbosity = getattr(args, "verbose", 0)
|
|
293
|
-
cluster_status = asyncio.run(_collect_cluster_status(node_addrs, verbosity))
|
|
294
|
-
|
|
295
|
-
# Basic node health check
|
|
296
|
-
print("Node Status:")
|
|
297
|
-
print("-" * 50)
|
|
298
|
-
all_healthy = True
|
|
299
|
-
|
|
300
|
-
valid_statuses = []
|
|
301
|
-
for status in cluster_status:
|
|
302
|
-
if isinstance(status, BaseException):
|
|
303
|
-
print(f"{'UNKNOWN':<15} {'UNKNOWN':<20} ERROR - {status}")
|
|
304
|
-
all_healthy = False
|
|
305
|
-
continue
|
|
306
|
-
|
|
307
|
-
valid_statuses.append(status)
|
|
308
|
-
node_id = status["node_id"]
|
|
309
|
-
endpoint = status["endpoint"]
|
|
310
|
-
|
|
311
|
-
if status["error"]:
|
|
312
|
-
print(f"{node_id:<15} {endpoint:<20} ERROR - {status['error']}")
|
|
313
|
-
all_healthy = False
|
|
314
|
-
elif status["healthy"]:
|
|
315
|
-
session_count = len(status["sessions"])
|
|
316
|
-
print(
|
|
317
|
-
f"{node_id:<15} {endpoint:<20} HEALTHY ({session_count} sessions)"
|
|
318
|
-
)
|
|
319
|
-
else:
|
|
320
|
-
print(f"{node_id:<15} {endpoint:<20} UNHEALTHY")
|
|
321
|
-
all_healthy = False
|
|
322
|
-
|
|
323
|
-
# If verbose mode is enabled, show detailed information
|
|
324
|
-
if verbosity >= 1 and valid_statuses:
|
|
325
|
-
print("\nDetailed Runtime Status:")
|
|
326
|
-
print("-" * 50)
|
|
327
|
-
|
|
328
|
-
for status in valid_statuses:
|
|
329
|
-
node_id = status["node_id"]
|
|
330
|
-
|
|
331
|
-
if status["error"]:
|
|
332
|
-
print(f"{node_id}: Error - {status['error']}")
|
|
333
|
-
continue
|
|
334
|
-
|
|
335
|
-
if not status["healthy"]:
|
|
336
|
-
print(f"{node_id}: Node is unhealthy")
|
|
337
|
-
continue
|
|
338
|
-
|
|
339
|
-
sessions = status.get("session_details", status["sessions"])
|
|
340
|
-
print(f"{node_id}: {len(sessions)} session(s)")
|
|
341
|
-
|
|
342
|
-
if isinstance(sessions, list) and sessions:
|
|
343
|
-
for session in sessions:
|
|
344
|
-
if isinstance(session, str):
|
|
345
|
-
# Simple session name only
|
|
346
|
-
print(f" - Session '{session}'")
|
|
347
|
-
elif isinstance(session, dict):
|
|
348
|
-
# Detailed session info
|
|
349
|
-
session_name = session["name"]
|
|
350
|
-
if "error" in session:
|
|
351
|
-
print(
|
|
352
|
-
f" - Session '{session_name}': Error - {session['error']}"
|
|
353
|
-
)
|
|
354
|
-
else:
|
|
355
|
-
computations = session.get("computations", 0)
|
|
356
|
-
symbols = session.get("symbols", 0)
|
|
357
|
-
print(
|
|
358
|
-
f" - Session '{session_name}': {computations} computations, {symbols} symbols"
|
|
359
|
-
)
|
|
360
|
-
# At -vv level, show the actual lists
|
|
361
|
-
if verbosity >= 2:
|
|
362
|
-
comp_list = session.get("computation_list", [])
|
|
363
|
-
symbol_list = session.get("symbol_list", [])
|
|
364
|
-
if comp_list:
|
|
365
|
-
print(f" Computations: {comp_list}")
|
|
366
|
-
if symbol_list:
|
|
367
|
-
print(f" Symbols: {symbol_list}")
|
|
368
|
-
elif not sessions:
|
|
369
|
-
print(" - No active sessions")
|
|
370
|
-
|
|
371
|
-
return 0 if all_healthy else 1
|
|
372
|
-
except Exception as e:
|
|
373
|
-
print(f"Error checking status: {e}")
|
|
374
|
-
return 1
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
def main() -> int:
|
|
378
|
-
"""Main entry point for the CLI."""
|
|
379
|
-
parser = argparse.ArgumentParser(
|
|
380
|
-
prog="mplang-cli",
|
|
381
|
-
description="Command-line interface for managing MPLang clusters",
|
|
382
|
-
)
|
|
383
|
-
|
|
384
|
-
# Add subcommands
|
|
385
|
-
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
386
|
-
|
|
387
|
-
# Help command
|
|
388
|
-
subparsers.add_parser("help", help="Show this help message and exit")
|
|
389
|
-
|
|
390
|
-
# Status command
|
|
391
|
-
status_parser = subparsers.add_parser(
|
|
392
|
-
"status", help="Check status of nodes in the cluster"
|
|
393
|
-
)
|
|
394
|
-
status_parser.add_argument(
|
|
395
|
-
"--config", "-c", required=True, help="Path to the YAML configuration file"
|
|
396
|
-
)
|
|
397
|
-
status_parser.add_argument(
|
|
398
|
-
"--verbose",
|
|
399
|
-
"-v",
|
|
400
|
-
action="count",
|
|
401
|
-
default=0,
|
|
402
|
-
help="Increase verbosity: -v for session details, -vv for full lists",
|
|
403
|
-
)
|
|
404
|
-
status_parser.set_defaults(func=status_command)
|
|
405
|
-
|
|
406
|
-
# Start command
|
|
407
|
-
start_parser = subparsers.add_parser("start", help="Start a single MPC node")
|
|
408
|
-
start_parser.add_argument(
|
|
409
|
-
"--config", "-c", required=True, help="Path to the YAML configuration file"
|
|
410
|
-
)
|
|
411
|
-
start_parser.add_argument(
|
|
412
|
-
"--node-id", "-n", required=True, type=str, help="ID of the node to start"
|
|
413
|
-
)
|
|
414
|
-
start_parser.set_defaults(func=start_command)
|
|
415
|
-
|
|
416
|
-
# Start cluster command
|
|
417
|
-
start_cluster_parser = subparsers.add_parser(
|
|
418
|
-
"start-cluster", help="Start all MPC nodes in the cluster"
|
|
419
|
-
)
|
|
420
|
-
start_cluster_parser.add_argument(
|
|
421
|
-
"--config", "-c", required=True, help="Path to the YAML configuration file"
|
|
422
|
-
)
|
|
423
|
-
start_cluster_parser.set_defaults(func=start_cluster_command)
|
|
424
|
-
|
|
425
|
-
# Up command (alias for start-cluster)
|
|
426
|
-
up_parser = subparsers.add_parser(
|
|
427
|
-
"up", help="Start all MPC nodes in the cluster (alias for start-cluster)"
|
|
428
|
-
)
|
|
429
|
-
up_parser.add_argument(
|
|
430
|
-
"--config", "-c", required=True, help="Path to the YAML configuration file"
|
|
431
|
-
)
|
|
432
|
-
up_parser.set_defaults(func=start_cluster_command)
|
|
433
|
-
|
|
434
|
-
# Parse arguments
|
|
435
|
-
args = parser.parse_args()
|
|
436
|
-
|
|
437
|
-
# Handle help command
|
|
438
|
-
if args.command == "help" or args.command is None:
|
|
439
|
-
parser.print_help()
|
|
440
|
-
return 0
|
|
441
|
-
|
|
442
|
-
# Handle subcommands
|
|
443
|
-
if hasattr(args, "func"):
|
|
444
|
-
result = args.func(args)
|
|
445
|
-
return int(result)
|
|
446
|
-
|
|
447
|
-
return 0
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if __name__ == "__main__":
|
|
451
|
-
sys.exit(main())
|