Skip to content

Commit 99382c6

Browse files
committed
Improve ability to inspect trains on hyperloop
1 parent 1a24064 commit 99382c6

1 file changed

Lines changed: 56 additions & 20 deletions

File tree

Framework/Core/scripts/hyperloop-server/hyperloop_server.py

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import json
3434
import os
3535
import sys
36+
import time
3637

3738
import httpx
3839
from mcp.server.fastmcp import FastMCP
@@ -51,7 +52,7 @@ def _headers() -> dict[str, str]:
5152
async def _get(path: str, params: dict | None = None) -> any:
5253
hdrs = _headers()
5354
hdrs["Accept-Encoding"] = "identity"
54-
async with httpx.AsyncClient(timeout=30) as client:
55+
async with httpx.AsyncClient(timeout=60) as client:
5556
r = await client.get(f"{API}/{path}", params=params, headers=hdrs)
5657
r.raise_for_status()
5758
return r.json()
@@ -93,24 +94,13 @@ def _parse_job_status(raw: str | None) -> dict:
9394
"active": active, "wait": max(0, wait)}
9495

9596

96-
@mcp.tool()
97-
async def list_ongoing_trains() -> str:
98-
"""List all currently running / ready Hyperloop train runs.
99-
100-
Returns a compact table with train ID, dataset, state, job progress,
101-
error rate, and package tag. One API call.
102-
"""
103-
trains = await _get("trains/all-trains.jsp", {"state": "ready"})
104-
if not trains:
105-
return "No ongoing trains."
106-
97+
def _format_train_table(trains: list[dict]) -> str:
10798
lines = []
10899
lines.append(f"{'ID':>8} {'State':<11} {'Done/Total':>12} {'Err%':>5} "
109100
f"{'Dataset':<40} {'Package'}")
110101
lines.append("-" * 120)
111102

112-
for t in sorted(trains, key=lambda x: _parse_job_status(
113-
x.get("job_status")).get("total", 0), reverse=True):
103+
for t in trains:
114104
js = _parse_job_status(t.get("job_status"))
115105
total = js.get("total", 0)
116106
done = js.get("done", 0)
@@ -125,19 +115,65 @@ async def list_ongoing_trains() -> str:
125115
f"{done:>6}/{total:<6} {err_pct:>5} "
126116
f"{ds:<40} {pkg}"
127117
)
128-
129-
lines.append(f"\nTotal: {len(trains)} trains")
130118
return "\n".join(lines)
131119

132120

121+
@mcp.tool()
122+
async def list_ongoing_trains() -> str:
123+
"""List all currently running / ready Hyperloop train runs.
124+
125+
Returns a compact table with train ID, dataset, state, job progress,
126+
error rate, and package tag. One API call.
127+
"""
128+
trains = await _get("trains/all-trains.jsp", {"state": "ready"})
129+
if not trains:
130+
return "No ongoing trains."
131+
132+
trains.sort(key=lambda x: _parse_job_status(
133+
x.get("job_status")).get("total", 0), reverse=True)
134+
135+
result = _format_train_table(trains)
136+
result += f"\n\nTotal: {len(trains)} trains"
137+
return result
138+
139+
140+
@mcp.tool()
141+
async def search_trains(dataset: str, last_n: int = 10) -> str:
142+
"""Search for recent trains (including finished) on a given dataset.
143+
144+
Uses the dataset name for server-side coarse filtering, then exact-matches
145+
client-side. Returns the most recent `last_n` trains (by ID descending).
146+
147+
Args:
148+
dataset: Exact dataset name (e.g. "LHC25ae_pass2_small").
149+
last_n: Number of most recent trains to return (default 10).
150+
"""
151+
raw = await _get("trains/all-trains.jsp", {"dataset_name": dataset})
152+
if not raw:
153+
return f"No trains found for dataset '{dataset}'."
154+
155+
# Server returns fuzzy matches; exact-filter client-side
156+
exact = [t for t in raw if t.get("dataset_name") == dataset]
157+
if not exact:
158+
return f"No trains found with exact dataset name '{dataset}'."
159+
160+
# Most recent first
161+
exact.sort(key=lambda t: t.get("id", 0), reverse=True)
162+
exact = exact[:last_n]
163+
164+
result = _format_train_table(exact)
165+
result += f"\n\nShowing {len(exact)} most recent (of {len([t for t in raw if t.get('dataset_name') == dataset])} total)"
166+
return result
167+
168+
133169
@mcp.tool()
134170
async def train_detail(train_id: int) -> str:
135-
"""Get resource metrics for a specific train run.
171+
"""Get resource metrics for a specific train run (ongoing or finished).
136172
137173
Shows CPU time, wall time, memory (PSS), throughput, input/output
138174
sizes, target, and merge status. One API call.
139175
"""
140-
t = await _get("trains/train.jsp", {"train_id": train_id, "type": "ready"})
176+
t = await _get("trains/train.jsp", {"train_id": train_id})
141177

142178
lines = [f"Train {t['id']}: {t.get('dataset_name', '?')}"]
143179
lines.append(f" State: {t.get('state')}")
@@ -169,13 +205,13 @@ async def train_detail(train_id: int) -> str:
169205

170206
@mcp.tool()
171207
async def wagon_stats(train_id: int) -> str:
172-
"""Get per-wagon CPU and memory breakdown for a train.
208+
"""Get per-wagon CPU and memory breakdown for a train (ongoing or finished).
173209
174210
Fetches wagon IDs from the train, then retrieves grid statistics
175211
for each wagon. Typically 10-20 wagons, one API call each.
176212
"""
177213
# First get train detail for dataset_id and wagons_timestamp
178-
t = await _get("trains/train.jsp", {"train_id": train_id, "type": "ready"})
214+
t = await _get("trains/train.jsp", {"train_id": train_id})
179215
dataset_id = t.get("dataset_id")
180216
wagons_ts = t.get("wagons_timestamp") or t.get("dataset_timestamp")
181217

0 commit comments

Comments
 (0)